github.com/prysmaticlabs/prysm@v1.4.4/tools/eth1exporter/main.go (about) 1 // Prometheus exporter for Ethereum address balances. 2 // Forked from https://github.com/hunterlong/ethexporter 3 package main 4 5 import ( 6 "bufio" 7 "context" 8 "flag" 9 "fmt" 10 "log" 11 "math/big" 12 "net/http" 13 "os" 14 "strings" 15 "time" 16 17 "github.com/ethereum/go-ethereum/common" 18 "github.com/ethereum/go-ethereum/ethclient" 19 "github.com/ethereum/go-ethereum/params" 20 _ "github.com/prysmaticlabs/prysm/shared/maxprocs" 21 "github.com/sirupsen/logrus" 22 ) 23 24 var ( 25 allWatching []*Watching 26 loadSeconds float64 27 totalLoaded int64 28 eth *ethclient.Client 29 ) 30 31 var ( 32 port = flag.Int("port", 9090, "Port to serve /metrics") 33 web3URL = flag.String("web3-provider", "https://goerli.prylabs.net", "Web3 URL to access information about ETH1") 34 prefix = flag.String("prefix", "", "Metrics prefix.") 35 addressFilePath = flag.String("addresses", "", "File path to addresses text file.") 36 ) 37 38 func main() { 39 flag.Parse() 40 41 if *addressFilePath == "" { 42 log.Println("--addresses is required") 43 return 44 } 45 46 err := OpenAddresses(*addressFilePath) 47 if err != nil { 48 panic(err) 49 } 50 51 err = ConnectionToGeth(*web3URL) 52 if err != nil { 53 panic(err) 54 } 55 56 // check address balances 57 go func() { 58 for { 59 totalLoaded = 0 60 t1 := time.Now() 61 fmt.Printf("Checking %v wallets...\n", len(allWatching)) 62 for _, v := range allWatching { 63 v.Balance = EthBalance(v.Address).String() 64 totalLoaded++ 65 } 66 t2 := time.Now() 67 loadSeconds = t2.Sub(t1).Seconds() 68 fmt.Printf("Finished checking %v wallets in %0.0f seconds, sleeping for %v seconds.\n", len(allWatching), loadSeconds, 15) 69 time.Sleep(15 * time.Second) 70 } 71 }() 72 73 block := CurrentBlock() 74 75 fmt.Printf("ETHexporter has started on port %v using web3 server: %v at block #%v\n", *port, *web3URL, block) 76 77 http.HandleFunc("/metrics", MetricsHTTP) 78 http.HandleFunc("/reload", ReloadHTTP) 79 log.Fatal(http.ListenAndServe(fmt.Sprintf("127.0.0.1:%d", *port), nil)) 80 } 81 82 // Watching address wrapper 83 type Watching struct { 84 Name string 85 Address string 86 Balance string 87 } 88 89 // ConnectionToGeth - Connect to remote server. 90 func ConnectionToGeth(url string) error { 91 var err error 92 eth, err = ethclient.Dial(url) 93 return err 94 } 95 96 // EthBalance from remote server. 97 func EthBalance(address string) *big.Float { 98 balance, err := eth.BalanceAt(context.TODO(), common.HexToAddress(address), nil) 99 if err != nil { 100 fmt.Printf("Error fetching ETH Balance for address: %v\n", address) 101 } 102 return ToEther(balance) 103 } 104 105 // CurrentBlock in ETH1. 106 func CurrentBlock() uint64 { 107 block, err := eth.BlockByNumber(context.TODO(), nil) 108 if err != nil { 109 fmt.Printf("Error fetching current block height: %v\n", err) 110 return 0 111 } 112 return block.NumberU64() 113 } 114 115 // ToEther from Wei. 116 func ToEther(o *big.Int) *big.Float { 117 wei := big.NewFloat(0) 118 wei.SetInt(o) 119 return new(big.Float).Quo(wei, big.NewFloat(params.Ether)) 120 } 121 122 // MetricsHTTP - HTTP response handler for /metrics. 123 func MetricsHTTP(w http.ResponseWriter, _ *http.Request) { 124 var allOut []string 125 total := big.NewFloat(0) 126 for _, v := range allWatching { 127 if v.Balance == "" { 128 v.Balance = "0" 129 } 130 bal := big.NewFloat(0) 131 bal.SetString(v.Balance) 132 total.Add(total, bal) 133 allOut = append(allOut, fmt.Sprintf("%veth_balance{name=\"%v\",address=\"%v\"} %v", *prefix, v.Name, v.Address, v.Balance)) 134 } 135 allOut = append(allOut, 136 fmt.Sprintf("%veth_balance_total %0.18f", *prefix, total), 137 fmt.Sprintf("%veth_load_seconds %0.2f", *prefix, loadSeconds), 138 fmt.Sprintf("%veth_loaded_addresses %v", *prefix, totalLoaded), 139 fmt.Sprintf("%veth_total_addresses %v", *prefix, len(allWatching))) 140 141 if _, err := fmt.Fprintln(w, strings.Join(allOut, "\n")); err != nil { 142 logrus.WithError(err).Error("Failed to write metrics") 143 } 144 } 145 146 // ReloadHTTP reloads the addresses from disk. 147 func ReloadHTTP(w http.ResponseWriter, _ *http.Request) { 148 if err := OpenAddresses(*addressFilePath); err != nil { 149 w.WriteHeader(http.StatusInternalServerError) 150 return 151 } 152 w.WriteHeader(http.StatusOK) 153 log.Println("Reloaded addresses") 154 } 155 156 // OpenAddresses from text file (name:address) 157 func OpenAddresses(filename string) error { 158 file, err := os.Open(filename) 159 if err != nil { 160 return err 161 } 162 defer func() { 163 if err := file.Close(); err != nil { 164 panic(err) 165 } 166 }() 167 scanner := bufio.NewScanner(file) 168 allWatching = []*Watching{} 169 for scanner.Scan() { 170 object := strings.Split(scanner.Text(), ":") 171 if common.IsHexAddress(object[1]) { 172 w := &Watching{ 173 Name: object[0], 174 Address: object[1], 175 } 176 allWatching = append(allWatching, w) 177 } 178 } 179 if err := scanner.Err(); err != nil { 180 return err 181 } 182 return err 183 }