github.com/linapex/ethereum-dpos-chinese@v0.0.0-20190316121959-b78b3a4a1ece/cmd/faucet/faucet.go (about) 1 2 //<developer> 3 // <name>linapex 曹一峰</name> 4 // <email>linapex@163.com</email> 5 // <wx>superexc</wx> 6 // <qqgroup>128148617</qqgroup> 7 // <url>https://jsq.ink</url> 8 // <role>pku engineer</role> 9 // <date>2019-03-16 12:09:28</date> 10 //</624342590287777792> 11 12 13 // 14 package main 15 16 //go:generate go bindata-nometadata-o website.go水龙头.html 17 //go:生成gofmt-w-s网站。go 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/json" 23 "errors" 24 "flag" 25 "fmt" 26 "html/template" 27 "io/ioutil" 28 "math" 29 "math/big" 30 "net/http" 31 "net/url" 32 "os" 33 "path/filepath" 34 "regexp" 35 "strconv" 36 "strings" 37 "sync" 38 "time" 39 40 "github.com/ethereum/go-ethereum/accounts" 41 "github.com/ethereum/go-ethereum/accounts/keystore" 42 "github.com/ethereum/go-ethereum/common" 43 "github.com/ethereum/go-ethereum/core" 44 "github.com/ethereum/go-ethereum/core/types" 45 "github.com/ethereum/go-ethereum/eth" 46 "github.com/ethereum/go-ethereum/eth/downloader" 47 "github.com/ethereum/go-ethereum/ethclient" 48 "github.com/ethereum/go-ethereum/ethstats" 49 "github.com/ethereum/go-ethereum/les" 50 "github.com/ethereum/go-ethereum/log" 51 "github.com/ethereum/go-ethereum/node" 52 "github.com/ethereum/go-ethereum/p2p" 53 "github.com/ethereum/go-ethereum/p2p/discover" 54 "github.com/ethereum/go-ethereum/p2p/discv5" 55 "github.com/ethereum/go-ethereum/p2p/nat" 56 "github.com/ethereum/go-ethereum/params" 57 "golang.org/x/net/websocket" 58 ) 59 60 61 62 63 var ( 64 genesisFlag = flag.String("genesis", "", "Genesis json file to seed the chain with") 65 apiPortFlag = flag.Int("apiport", 8080, "Listener port for the HTTP API connection") 66 ethPortFlag = flag.Int("ethport", 30303, "Listener port for the devp2p connection") 67 bootFlag = flag.String("bootnodes", "", "Comma separated bootnode enode URLs to seed with") 68 netFlag = flag.Uint64("network", 0, "Network ID to use for the Ethereum protocol") 69 statsFlag = flag.String("ethstats", "", "Ethstats network monitoring auth string") 70 71 netnameFlag = flag.String("faucet.name", "", "Network name to assign to the faucet") 72 payoutFlag = flag.Int("faucet.amount", 1, "Number of Ethers to pay out per user request") 73 minutesFlag = flag.Int("faucet.minutes", 1440, "Number of minutes to wait between funding rounds") 74 tiersFlag = flag.Int("faucet.tiers", 3, "Number of funding tiers to enable (x3 time, x2.5 funds)") 75 76 accJSONFlag = flag.String("account.json", "", "Key json file to fund user requests with") 77 accPassFlag = flag.String("account.pass", "", "Decryption password to access faucet funds") 78 79 captchaToken = flag.String("captcha.token", "", "Recaptcha site key to authenticate client side") 80 captchaSecret = flag.String("captcha.secret", "", "Recaptcha secret key to authenticate server side") 81 82 noauthFlag = flag.Bool("noauth", false, "Enables funding requests without authentication") 83 logFlag = flag.Int("loglevel", 3, "Log level to use for Ethereum and the faucet") 84 ) 85 86 var ( 87 ether = new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil) 88 ) 89 90 func main() { 91 //分析标志并设置记录器以打印所请求的所有内容 92 flag.Parse() 93 log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*logFlag), log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) 94 95 //构建支出层 96 amounts := make([]string, *tiersFlag) 97 periods := make([]string, *tiersFlag) 98 for i := 0; i < *tiersFlag; i++ { 99 //计算下一层的金额并格式化 100 amount := float64(*payoutFlag) * math.Pow(2.5, float64(i)) 101 amounts[i] = fmt.Sprintf("%s Ethers", strconv.FormatFloat(amount, 'f', -1, 64)) 102 if amount == 1 { 103 amounts[i] = strings.TrimSuffix(amounts[i], "s") 104 } 105 //计算下一层的期间并设置其格式 106 period := *minutesFlag * int(math.Pow(3, float64(i))) 107 periods[i] = fmt.Sprintf("%d mins", period) 108 if period%60 == 0 { 109 period /= 60 110 periods[i] = fmt.Sprintf("%d hours", period) 111 112 if period%24 == 0 { 113 period /= 24 114 periods[i] = fmt.Sprintf("%d days", period) 115 } 116 } 117 if period == 1 { 118 periods[i] = strings.TrimSuffix(periods[i], "s") 119 } 120 } 121 //加载并呈现水龙头网站 122 tmpl, err := Asset("faucet.html") 123 if err != nil { 124 log.Crit("Failed to load the faucet template", "err", err) 125 } 126 website := new(bytes.Buffer) 127 err = template.Must(template.New("").Parse(string(tmpl))).Execute(website, map[string]interface{}{ 128 "Network": *netnameFlag, 129 "Amounts": amounts, 130 "Periods": periods, 131 "Recaptcha": *captchaToken, 132 "NoAuth": *noauthFlag, 133 }) 134 if err != nil { 135 log.Crit("Failed to render the faucet template", "err", err) 136 } 137 //加载并分析用户请求的Genesis块 138 blob, err := ioutil.ReadFile(*genesisFlag) 139 if err != nil { 140 log.Crit("Failed to read genesis block contents", "genesis", *genesisFlag, "err", err) 141 } 142 genesis := new(core.Genesis) 143 if err = json.Unmarshal(blob, genesis); err != nil { 144 log.Crit("Failed to parse genesis block json", "err", err) 145 } 146 //将bootnode转换为内部enode表示形式 147 var enodes []*discv5.Node 148 for _, boot := range strings.Split(*bootFlag, ",") { 149 if url, err := discv5.ParseNode(boot); err == nil { 150 enodes = append(enodes, url) 151 } else { 152 log.Error("Failed to parse bootnode URL", "url", boot, "err", err) 153 } 154 } 155 //加载帐户密钥并解密其密码 156 if blob, err = ioutil.ReadFile(*accPassFlag); err != nil { 157 log.Crit("Failed to read account password contents", "file", *accPassFlag, "err", err) 158 } 159 pass := string(blob) 160 161 ks := keystore.NewKeyStore(filepath.Join(os.Getenv("HOME"), ".faucet", "keys"), keystore.StandardScryptN, keystore.StandardScryptP) 162 if blob, err = ioutil.ReadFile(*accJSONFlag); err != nil { 163 log.Crit("Failed to read account key contents", "file", *accJSONFlag, "err", err) 164 } 165 acc, err := ks.Import(blob, pass, pass) 166 if err != nil { 167 log.Crit("Failed to import faucet signer account", "err", err) 168 } 169 ks.Unlock(acc, pass) 170 171 //组装并启动水龙头照明服务 172 faucet, err := newFaucet(genesis, *ethPortFlag, enodes, *netFlag, *statsFlag, ks, website.Bytes()) 173 if err != nil { 174 log.Crit("Failed to start faucet", "err", err) 175 } 176 defer faucet.close() 177 178 if err := faucet.listenAndServe(*apiPortFlag); err != nil { 179 log.Crit("Failed to launch faucet API", "err", err) 180 } 181 } 182 183 //请求表示已接受的资金请求。 184 type request struct { 185 Avatar string `json:"avatar"` //使用户界面更美好的虚拟人物URL 186 Account common.Address `json:"account"` //正在资助以太坊地址 187 Time time.Time `json:"time"` //接受请求时的时间戳 188 Tx *types.Transaction `json:"tx"` //为账户提供资金的交易 189 } 190 191 // 192 type faucet struct { 193 config *params.ChainConfig //签名的链配置 194 stack *node.Node //以太坊协议栈 195 client *ethclient.Client // 196 index []byte //在网上提供的索引页 197 198 keystore *keystore.KeyStore //包含单个签名者的密钥库 199 account accounts.Account //帐户资金用户水龙头请求 200 nonce uint64 //水龙头的当前挂起时间 201 price *big.Int //发行资金的当前天然气价格 202 203 conns []*websocket.Conn //当前活动的WebSocket连接 204 timeouts map[string]time.Time //用户历史及其资金超时 205 reqs []*request //当前待定的资金请求 206 update chan struct{} //通道到信号请求更新 207 208 lock sync.RWMutex //锁保护水龙头内部 209 } 210 211 func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) { 212 //组装原始devp2p协议栈 213 stack, err := node.New(&node.Config{ 214 Name: "geth", 215 Version: params.VersionWithMeta, 216 DataDir: filepath.Join(os.Getenv("HOME"), ".faucet"), 217 P2P: p2p.Config{ 218 NAT: nat.Any(), 219 NoDiscovery: true, 220 DiscoveryV5: true, 221 ListenAddr: fmt.Sprintf(":%d", port), 222 MaxPeers: 25, 223 BootstrapNodesV5: enodes, 224 }, 225 }) 226 if err != nil { 227 return nil, err 228 } 229 //组装以太坊Light客户端协议 230 if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { 231 cfg := eth.DefaultConfig 232 cfg.SyncMode = downloader.LightSync 233 cfg.NetworkId = network 234 cfg.Genesis = genesis 235 return les.New(ctx, &cfg) 236 }); err != nil { 237 return nil, err 238 } 239 //组装ethstats监视和报告服务' 240 if stats != "" { 241 if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { 242 var serv *les.LightEthereum 243 ctx.Service(&serv) 244 return ethstats.New(stats, nil, serv) 245 }); err != nil { 246 return nil, err 247 } 248 } 249 //启动客户机并确保它连接到引导节点 250 if err := stack.Start(); err != nil { 251 return nil, err 252 } 253 for _, boot := range enodes { 254 old, _ := discover.ParseNode(boot.String()) 255 stack.Server().AddPeer(old) 256 } 257 //附加到客户端并检索有趣的元数据 258 api, err := stack.Attach() 259 if err != nil { 260 stack.Stop() 261 return nil, err 262 } 263 client := ethclient.NewClient(api) 264 265 return &faucet{ 266 config: genesis.Config, 267 stack: stack, 268 client: client, 269 index: index, 270 keystore: ks, 271 account: ks.Accounts()[0], 272 timeouts: make(map[string]time.Time), 273 update: make(chan struct{}, 1), 274 }, nil 275 } 276 277 //关闭会终止以太坊连接并将水龙头拆下。 278 func (f *faucet) close() error { 279 return f.stack.Stop() 280 } 281 282 //listenandserve注册水龙头的HTTP处理程序并启动它。 283 //服务用户资金请求。 284 func (f *faucet) listenAndServe(port int) error { 285 go f.loop() 286 287 http.HandleFunc("/", f.webHandler) 288 http.Handle("/api", websocket.Handler(f.apiHandler)) 289 290 return http.ListenAndServe(fmt.Sprintf(":%d", port), nil) 291 } 292 293 //WebHandler处理所有非API请求,只需扁平化并返回 294 //水龙头网站。 295 func (f *faucet) webHandler(w http.ResponseWriter, r *http.Request) { 296 w.Write(f.index) 297 } 298 299 //apiHandler处理乙醚授权和事务状态的请求。 300 func (f *faucet) apiHandler(conn *websocket.Conn) { 301 //开始跟踪连接并在末尾放置 302 defer conn.Close() 303 304 f.lock.Lock() 305 f.conns = append(f.conns, conn) 306 f.lock.Unlock() 307 308 defer func() { 309 f.lock.Lock() 310 for i, c := range f.conns { 311 if c == conn { 312 f.conns = append(f.conns[:i], f.conns[i+1:]...) 313 break 314 } 315 } 316 f.lock.Unlock() 317 }() 318 //从网络收集初始统计数据以进行报告 319 var ( 320 head *types.Header 321 balance *big.Int 322 nonce uint64 323 err error 324 ) 325 for { 326 //尝试检索统计信息,可能在没有水龙头连接时出错。 327 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 328 head, err = f.client.HeaderByNumber(ctx, nil) 329 if err == nil { 330 balance, err = f.client.BalanceAt(ctx, f.account.Address, head.Number) 331 if err == nil { 332 nonce, err = f.client.NonceAt(ctx, f.account.Address, nil) 333 } 334 } 335 cancel() 336 337 //如果状态检索失败,请稍等片刻,然后重试。 338 if err != nil { 339 if err = sendError(conn, errors.New("Faucet offline: "+err.Error())); err != nil { 340 log.Warn("Failed to send faucet error to client", "err", err) 341 return 342 } 343 time.Sleep(3 * time.Second) 344 continue 345 } 346 //已成功报告初始状态,继续用户交互 347 break 348 } 349 //发送初始数据和最新标题 350 if err = send(conn, map[string]interface{}{ 351 "funds": balance.Div(balance, ether), 352 "funded": nonce, 353 "peers": f.stack.Server().PeerCount(), 354 "requests": f.reqs, 355 }, 3*time.Second); err != nil { 356 log.Warn("Failed to send initial stats to client", "err", err) 357 return 358 } 359 if err = send(conn, head, 3*time.Second); err != nil { 360 log.Warn("Failed to send initial header to client", "err", err) 361 return 362 } 363 //继续从WebSocket读取请求,直到连接断开 364 for { 365 //获取下一个融资请求并根据Github进行验证 366 var msg struct { 367 URL string `json:"url"` 368 Tier uint `json:"tier"` 369 Captcha string `json:"captcha"` 370 } 371 if err = websocket.JSON.Receive(conn, &msg); err != nil { 372 return 373 } 374 if !*noauthFlag && !strings.HasPrefix(msg.URL, "https:// 375 !strings.HasPrefix(msg.URL, "https://另外,google.com/“)&&!strings.hasPrefix(msg.url,“https://www.facebook.com/”) 376 if err = sendError(conn, errors.New("URL doesn't link to supported services")); err != nil { 377 log.Warn("Failed to send URL error to client", "err", err) 378 return 379 } 380 continue 381 } 382 if msg.Tier >= uint(*tiersFlag) { 383 if err = sendError(conn, errors.New("Invalid funding tier requested")); err != nil { 384 log.Warn("Failed to send tier error to client", "err", err) 385 return 386 } 387 continue 388 } 389 log.Info("Faucet funds requested", "url", msg.URL, "tier", msg.Tier) 390 391 //如果验证码验证被启用,确保我们没有处理机器人 392 if *captchaToken != "" { 393 form := url.Values{} 394 form.Add("secret", *captchaSecret) 395 form.Add("response", msg.Captcha) 396 397 res, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify“,表单) 398 if err != nil { 399 if err = sendError(conn, err); err != nil { 400 log.Warn("Failed to send captcha post error to client", "err", err) 401 return 402 } 403 continue 404 } 405 var result struct { 406 Success bool `json:"success"` 407 Errors json.RawMessage `json:"error-codes"` 408 } 409 err = json.NewDecoder(res.Body).Decode(&result) 410 res.Body.Close() 411 if err != nil { 412 if err = sendError(conn, err); err != nil { 413 log.Warn("Failed to send captcha decode error to client", "err", err) 414 return 415 } 416 continue 417 } 418 if !result.Success { 419 log.Warn("Captcha verification failed", "err", string(result.Errors)) 420 if err = sendError(conn, errors.New("Beep-bop, you're a robot!")); err != nil { 421 log.Warn("Failed to send captcha failure to client", "err", err) 422 return 423 } 424 continue 425 } 426 } 427 //检索以太坊资金地址、请求用户和个人资料图片 428 var ( 429 username string 430 avatar string 431 address common.Address 432 ) 433 switch { 434 case strings.HasPrefix(msg.URL, "https://gist.github.com/“): 435 if err = sendError(conn, errors.New("GitHub authentication discontinued at the official request of GitHub")); err != nil { 436 log.Warn("Failed to send GitHub deprecation to client", "err", err) 437 return 438 } 439 continue 440 case strings.HasPrefix(msg.URL, "https://Twitter .com /(): 441 username, avatar, address, err = authTwitter(msg.URL) 442 case strings.HasPrefix(msg.URL, "https://加上google.com/“): 443 username, avatar, address, err = authGooglePlus(msg.URL) 444 case strings.HasPrefix(msg.URL, "https://www.facebook.com/“): 445 username, avatar, address, err = authFacebook(msg.URL) 446 case *noauthFlag: 447 username, avatar, address, err = authNoAuth(msg.URL) 448 default: 449 err = errors.New("Something funky happened, please open an issue at https://github.com/haxicode/go-ethereum/issues“) 450 } 451 if err != nil { 452 if err = sendError(conn, err); err != nil { 453 log.Warn("Failed to send prefix error to client", "err", err) 454 return 455 } 456 continue 457 } 458 log.Info("Faucet request valid", "url", msg.URL, "tier", msg.Tier, "user", username, "address", address) 459 460 //确保用户最近没有申请资金 461 f.lock.Lock() 462 var ( 463 fund bool 464 timeout time.Time 465 ) 466 if timeout = f.timeouts[username]; time.Now().After(timeout) { 467 //用户最近没有资金,创建资金交易 468 amount := new(big.Int).Mul(big.NewInt(int64(*payoutFlag)), ether) 469 amount = new(big.Int).Mul(amount, new(big.Int).Exp(big.NewInt(5), big.NewInt(int64(msg.Tier)), nil)) 470 amount = new(big.Int).Div(amount, new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(msg.Tier)), nil)) 471 472 tx := types.NewTransaction(types.Binary, f.nonce+uint64(len(f.reqs)), address, amount, 21000, f.price, nil) 473 signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainID) 474 if err != nil { 475 f.lock.Unlock() 476 if err = sendError(conn, err); err != nil { 477 log.Warn("Failed to send transaction creation error to client", "err", err) 478 return 479 } 480 continue 481 } 482 // 483 if err := f.client.SendTransaction(context.Background(), signed); err != nil { 484 f.lock.Unlock() 485 if err = sendError(conn, err); err != nil { 486 log.Warn("Failed to send transaction transmission error to client", "err", err) 487 return 488 } 489 continue 490 } 491 f.reqs = append(f.reqs, &request{ 492 Avatar: avatar, 493 Account: address, 494 Time: time.Now(), 495 Tx: signed, 496 }) 497 f.timeouts[username] = time.Now().Add(time.Duration(*minutesFlag*int(math.Pow(3, float64(msg.Tier)))) * time.Minute) 498 fund = true 499 } 500 f.lock.Unlock() 501 502 //如果融资过于频繁,则发送错误,否则将成功 503 if !fund { 504 if err = sendError(conn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(timeout.Sub(time.Now())))); err != nil { //诺林:天哪 505 log.Warn("Failed to send funding error to client", "err", err) 506 return 507 } 508 continue 509 } 510 if err = sendSuccess(conn, fmt.Sprintf("Funding request accepted for %s into %s", username, address.Hex())); err != nil { 511 log.Warn("Failed to send funding success to client", "err", err) 512 return 513 } 514 select { 515 case f.update <- struct{}{}: 516 default: 517 } 518 } 519 } 520 521 //循环一直在等待有趣的事件,并将它们推出到Connected 522 //WebSoCukes。 523 func (f *faucet) loop() { 524 //等待链事件并将其推送到客户端 525 heads := make(chan *types.Header, 16) 526 sub, err := f.client.SubscribeNewHead(context.Background(), heads) 527 if err != nil { 528 log.Crit("Failed to subscribe to head events", "err", err) 529 } 530 defer sub.Unsubscribe() 531 532 //启动goroutine以从后台的头通知更新状态 533 update := make(chan *types.Header) 534 535 go func() { 536 for head := range update { 537 //新的链头到达,查询当前状态并流到客户端 538 var ( 539 balance *big.Int 540 nonce uint64 541 price *big.Int 542 err error 543 ) 544 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 545 balance, err = f.client.BalanceAt(ctx, f.account.Address, head.Number) 546 if err == nil { 547 nonce, err = f.client.NonceAt(ctx, f.account.Address, nil) 548 if err == nil { 549 price, err = f.client.SuggestGasPrice(ctx) 550 } 551 } 552 cancel() 553 554 //如果查询数据失败,请尝试下一个块 555 if err != nil { 556 log.Warn("Failed to update faucet state", "block", head.Number, "hash", head.Hash(), "err", err) 557 continue 558 } else { 559 log.Info("Updated faucet state", "block", head.Number, "hash", head.Hash(), "balance", balance, "nonce", nonce, "price", price) 560 } 561 //检索水龙头状态,本地更新并发送给客户 562 balance = new(big.Int).Div(balance, ether) 563 564 f.lock.Lock() 565 f.price, f.nonce = price, nonce 566 for len(f.reqs) > 0 && f.reqs[0].Tx.Nonce() < f.nonce { 567 f.reqs = f.reqs[1:] 568 } 569 f.lock.Unlock() 570 571 f.lock.RLock() 572 for _, conn := range f.conns { 573 if err := send(conn, map[string]interface{}{ 574 "funds": balance, 575 "funded": f.nonce, 576 "peers": f.stack.Server().PeerCount(), 577 "requests": f.reqs, 578 }, time.Second); err != nil { 579 log.Warn("Failed to send stats to client", "err", err) 580 conn.Close() 581 continue 582 } 583 if err := send(conn, head, time.Second); err != nil { 584 log.Warn("Failed to send header to client", "err", err) 585 conn.Close() 586 } 587 } 588 f.lock.RUnlock() 589 } 590 }() 591 //等待各种事件并分配到适当的后台线程 592 for { 593 select { 594 case head := <-heads: 595 //新的头已到达,如果没有运行则发送if以进行状态更新 596 select { 597 case update <- head: 598 default: 599 } 600 601 case <-f.update: 602 //更新了挂起的请求,流式传输到客户端 603 f.lock.RLock() 604 for _, conn := range f.conns { 605 if err := send(conn, map[string]interface{}{"requests": f.reqs}, time.Second); err != nil { 606 log.Warn("Failed to send requests to client", "err", err) 607 conn.Close() 608 } 609 } 610 f.lock.RUnlock() 611 } 612 } 613 } 614 615 //发送数据包到WebSocket的远程端,但也 616 //设置写入截止时间以防止永远等待在节点上。 617 func send(conn *websocket.Conn, value interface{}, timeout time.Duration) error { 618 if timeout == 0 { 619 timeout = 60 * time.Second 620 } 621 conn.SetWriteDeadline(time.Now().Add(timeout)) 622 return websocket.JSON.Send(conn, value) 623 } 624 625 //sendError将错误传输到WebSocket的远程端,同时设置 626 //写截止时间为1秒,以防永远等待。 627 func sendError(conn *websocket.Conn, err error) error { 628 return send(conn, map[string]string{"error": err.Error()}, time.Second) 629 } 630 631 //sendssuccess还将成功消息发送到websocket的远程端 632 //将写入截止时间设置为1秒,以防止永远等待。 633 func sendSuccess(conn *websocket.Conn, msg string) error { 634 return send(conn, map[string]string{"success": msg}, time.Second) 635 } 636 637 //AuthTwitter尝试使用Twitter帖子验证水龙头请求,返回 638 //用户名、虚拟人物URL和以太坊地址将在成功时提供资金。 639 func authTwitter(url string) (string, string, common.Address, error) { 640 //确保用户指定了一个有意义的URL,没有花哨的胡说八道。 641 parts := strings.Split(url, "/") 642 if len(parts) < 4 || parts[len(parts)-2] != "status" { 643 return "", "", common.Address{}, errors.New("Invalid Twitter status URL") 644 } 645 // 646 //想做的是询问用户的读权限,所以只需加载公共文章和 647 //从以太坊地址和配置文件URL中清除它。 648 res, err := http.Get(url) 649 if err != nil { 650 return "", "", common.Address{}, err 651 } 652 defer res.Body.Close() 653 654 //从最终重定向中解析用户名,无中间垃圾邮件 655 parts = strings.Split(res.Request.URL.String(), "/") 656 if len(parts) < 4 || parts[len(parts)-2] != "status" { 657 return "", "", common.Address{}, errors.New("Invalid Twitter status URL") 658 } 659 username := parts[len(parts)-3] 660 661 body, err := ioutil.ReadAll(res.Body) 662 if err != nil { 663 return "", "", common.Address{}, err 664 } 665 address := common.HexToAddress(string(regexp.MustCompile("0x[0-9a-fA-F]{40}").Find(body))) 666 if address == (common.Address{}) { 667 return "", "", common.Address{}, errors.New("No Ethereum address found to fund") 668 } 669 var avatar string 670 if parts = regexp.MustCompile("src=\"([^\"]+twimg.com/profile_images[^\"]+)\"").FindStringSubmatch(string(body)); len(parts) == 2 { 671 avatar = parts[1] 672 } 673 return username + "@twitter", avatar, address, nil 674 } 675 676 //authgoogleplus尝试使用googleplus帖子验证水龙头请求, 677 //成功后返回用户名、虚拟人物URL和以太坊地址进行投资。 678 func authGooglePlus(url string) (string, string, common.Address, error) { 679 //确保用户指定了一个有意义的URL,没有花哨的胡说八道。 680 parts := strings.Split(url, "/") 681 if len(parts) < 4 || parts[len(parts)-2] != "posts" { 682 return "", "", common.Address{}, errors.New("Invalid Google+ post URL") 683 } 684 username := parts[len(parts)-3] 685 686 //谷歌的API对直接链接不是很友好。但是,我们没有 687 //想做的是询问用户的读权限,所以只需加载公共文章和 688 //从以太坊地址和配置文件URL中清除它。 689 res, err := http.Get(url) 690 if err != nil { 691 return "", "", common.Address{}, err 692 } 693 defer res.Body.Close() 694 695 body, err := ioutil.ReadAll(res.Body) 696 if err != nil { 697 return "", "", common.Address{}, err 698 } 699 address := common.HexToAddress(string(regexp.MustCompile("0x[0-9a-fA-F]{40}").Find(body))) 700 if address == (common.Address{}) { 701 return "", "", common.Address{}, errors.New("No Ethereum address found to fund") 702 } 703 var avatar string 704 if parts = regexp.MustCompile("src=\"([^\"]+googleusercontent.com[^\"]+photo.jpg)\"").FindStringSubmatch(string(body)); len(parts) == 2 { 705 avatar = parts[1] 706 } 707 return username + "@google+", avatar, address, nil 708 } 709 710 //AuthFacebook尝试使用Facebook帖子验证水龙头请求, 711 //成功后返回用户名、虚拟人物URL和以太坊地址进行投资。 712 func authFacebook(url string) (string, string, common.Address, error) { 713 //确保用户指定了一个有意义的URL,没有花哨的胡说八道。 714 parts := strings.Split(url, "/") 715 if len(parts) < 4 || parts[len(parts)-2] != "posts" { 716 return "", "", common.Address{}, errors.New("Invalid Facebook post URL") 717 } 718 username := parts[len(parts)-3] 719 720 //Facebook的图形API对直接链接不太友好。但是,我们没有 721 //想做的是询问用户的读权限,所以只需加载公共文章和 722 //从以太坊地址和配置文件URL中清除它。 723 res, err := http.Get(url) 724 if err != nil { 725 return "", "", common.Address{}, err 726 } 727 defer res.Body.Close() 728 729 body, err := ioutil.ReadAll(res.Body) 730 if err != nil { 731 return "", "", common.Address{}, err 732 } 733 address := common.HexToAddress(string(regexp.MustCompile("0x[0-9a-fA-F]{40}").Find(body))) 734 if address == (common.Address{}) { 735 return "", "", common.Address{}, errors.New("No Ethereum address found to fund") 736 } 737 var avatar string 738 if parts = regexp.MustCompile("src=\"([^\"]+fbcdn.net[^\"]+)\"").FindStringSubmatch(string(body)); len(parts) == 2 { 739 avatar = parts[1] 740 } 741 return username + "@facebook", avatar, address, nil 742 } 743 744 //AuthNoAuth试图将水龙头请求解释为一个普通的以太坊地址, 745 //没有实际执行任何远程身份验证。这种模式很容易 746 //拜占庭式攻击,所以只能用于真正的私人网络。 747 func authNoAuth(url string) (string, string, common.Address, error) { 748 address := common.HexToAddress(regexp.MustCompile("0x[0-9a-fA-F]{40}").FindString(url)) 749 if address == (common.Address{}) { 750 return "", "", common.Address{}, errors.New("No Ethereum address found to fund") 751 } 752 return address.Hex() + "@noauth", "", address, nil 753 } 754