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