From 8cbc6670ccb0b9e0aa5240252debf0b42156b1f9 Mon Sep 17 00:00:00 2001 From: Svarog Date: Sun, 24 Nov 2019 13:03:26 +0200 Subject: [PATCH] [NOD-445] Enable using EC2 AutoScalingGroup to get list of btcds for mining simulator (#484) * [NOD-445] Added option to mining simulator to get address list from AWS * [NOD-445] Add support to get miningsimulator addresslist from AWS * [NOD-445] Added mechanism to update when new servers come online * [NOD-445] Set config in connectionManager * [NOD-445] Invert DisableTLS condition in readCert --- docker/rpc.cert | 30 +++---- docker/rpc.key | 10 +-- go.mod | 1 + go.sum | 4 + mining/simulator/addresslist.go | 43 +++++++++ mining/simulator/config.go | 25 ++++-- mining/simulator/conn_manager.go | 149 +++++++++++++++++++++++++++++++ mining/simulator/connect.go | 47 ---------- mining/simulator/main.go | 16 ++-- mining/simulator/mineloop.go | 9 +- 10 files changed, 248 insertions(+), 86 deletions(-) create mode 100644 mining/simulator/conn_manager.go delete mode 100644 mining/simulator/connect.go diff --git a/docker/rpc.cert b/docker/rpc.cert index 28bcea418..250e672f3 100644 --- a/docker/rpc.cert +++ b/docker/rpc.cert @@ -1,17 +1,17 @@ -----BEGIN CERTIFICATE----- -MIICnzCCAgCgAwIBAgIRAMtb0YzwW1FdMNf8T0UNqR8wCgYIKoZIzj0EAwQwLTER -MA8GA1UEChMIZ2VuY2VydHMxGDAWBgNVBAMTD3VzZXItVmlydHVhbEJveDAeFw0x -OTAzMzAwODQ1MjhaFw0yOTAzMjgwODQ1MjhaMC0xETAPBgNVBAoTCGdlbmNlcnRz -MRgwFgYDVQQDEw91c2VyLVZpcnR1YWxCb3gwgZswEAYHKoZIzj0CAQYFK4EEACMD -gYYABAFQTRhFGAfgyfYoxCfTCoaWKYhABQ3h+JScx+oTvGfC8kVc2s7JLckOJN9u -tQTHMwrhx2ElTGpuC3vHFsuUlAtBDgF//YJUAF5fv3IQTVkSUECUp4uLYMbmvcLQ -gBE1a/jduTT8Dfhjd5SV+sV0TOxqjf+Qkb66oti1ruFvRTiWy+hu26OBvTCBujAO -BgNVHQ8BAf8EBAMCAqQwDwYDVR0TAQH/BAUwAwEB/zCBlgYDVR0RBIGOMIGLgg91 -c2VyLVZpcnR1YWxCb3iCCWxvY2FsaG9zdIINKi5kYWdsYWJzLmNvbYcEfwAAAYcQ -AAAAAAAAAAAAAAAAAAAAAYcECgACD4cErBEAAYcErBvoNYcQ/oAAAAAAAABT1E8K -u3k8v4cQ/oAAAAAAAAAAQtb//ntyVocQ/oAAAAAAAABC3PMbkAyPcjAKBggqhkjO -PQQDBAOBjAAwgYgCQgD4aX3+8HCaPkUMLBRFrZ2BLP8CUeoBwSaD77Yw9vOtSEfL -Q/RMJKCG7SqDD6KnoJh6suwQwNJ6TelFWz8y2woBBwJCAJXET2Xeh5Ez7mV+geCv -7yyO7IjaNXMidyIDb+MxonvePSw4twH7uQSMcAJM97LB52tLWMhxidQ1g9ShhNM8 -up7L +MIICszCCAhSgAwIBAgIQROJgDcPic4zOu5pqHnLL6jAKBggqhkjOPQQDBDAnMREw +DwYDVQQKEwhnZW5jZXJ0czESMBAGA1UEAxMJbWlrZWJ1bnR1MB4XDTE5MTExOTA4 +NDYwNFoXDTI5MTExNzA4NDYwNFowJzERMA8GA1UEChMIZ2VuY2VydHMxEjAQBgNV +BAMTCW1pa2VidW50dTCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEATjMf/8pPRxm +BLU02cM9drNLds8fyW1YOtDE+LItQ59nC0dvEZhnYcotWy4TRZ0b/SYbSfg+/7CG +yYJWaTAPFPk3Abd6rKl0kfTbp6mqpmn4Jz6YbDCmAkGrSjGpZMePDeUZ+SAgR1MX +Kezy9UxP4214jFgZQO6u9iOIoueNE/5j6J0eo4HeMIHbMA4GA1UdDwEB/wQEAwIC +pDAPBgNVHRMBAf8EBTADAQH/MIG3BgNVHREEga8wgayCCW1pa2VidW50dYIJbG9j +YWxob3N0gh8qLmV1LWNlbnRyYWwtMS5jb21wdXRlLmludGVybmFsgg0qLmRhZ2xh +YnMuY29thwR/AAABhxAAAAAAAAAAAAAAAAAAAAABhwTAqACBhwSsEQABhwSsEgAB +hwSsG+pBhxD+gAAAAAAAAEDwidLhbKZ1hxD+gAAAAAAAAABC4//+dS5UhxD+gAAA +AAAAAC3rJp/UOkuVMAoGCCqGSM49BAMEA4GMADCBiAJCAeXt5Jyl/92QrTxvdQZc +fPBX28gjiG4/XTM0NdAqc2BcUGElDUtdZJ1yE6aTYdMffeaNUyk0lJ33VJWc9RB6 +iOxkAkIBdg5LzImJTqunDEVJPB/hB+9P8SYJdT/Xq/uXZrgDUOd74qhrQeV6PnyT +MRR4d4Cfd51i8Q45brQPjKSdoZi8bzg= -----END CERTIFICATE----- diff --git a/docker/rpc.key b/docker/rpc.key index 659d119e3..9fd89ee48 100644 --- a/docker/rpc.key +++ b/docker/rpc.key @@ -1,7 +1,7 @@ -----BEGIN EC PRIVATE KEY----- -MIHcAgEBBEIBaKKSYIpYlc1ERcfWQ7ljkHBLEyQM51JIdhQ1cYJjiMrW9W0FB3zY -1lF6RMIOmE/vXV8B941q7Vyg8TylAMcKPCygBwYFK4EEACOhgYkDgYYABAFQTRhF -GAfgyfYoxCfTCoaWKYhABQ3h+JScx+oTvGfC8kVc2s7JLckOJN9utQTHMwrhx2El -TGpuC3vHFsuUlAtBDgF//YJUAF5fv3IQTVkSUECUp4uLYMbmvcLQgBE1a/jduTT8 -Dfhjd5SV+sV0TOxqjf+Qkb66oti1ruFvRTiWy+hu2w== +MIHcAgEBBEIBdqJs4hINoLpeYY3wFvcIWknsP30kWT94oANqTNdrcc7UkSj7s8Xv +hPqT6hNJxae9GCkuVg9lydRRaPL+Lxva8DegBwYFK4EEACOhgYkDgYYABAE4zH// +KT0cZgS1NNnDPXazS3bPH8ltWDrQxPiyLUOfZwtHbxGYZ2HKLVsuE0WdG/0mG0n4 +Pv+whsmCVmkwDxT5NwG3eqypdJH026epqqZp+Cc+mGwwpgJBq0oxqWTHjw3lGfkg +IEdTFyns8vVMT+NteIxYGUDurvYjiKLnjRP+Y+idHg== -----END EC PRIVATE KEY----- diff --git a/go.mod b/go.mod index d491efa00..c8410f195 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.13 require ( bou.ke/monkey v1.0.1 github.com/aead/siphash v1.0.1 + github.com/aws/aws-sdk-go v1.25.37 github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd github.com/btcsuite/goleveldb v1.0.0 github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 diff --git a/go.sum b/go.sum index 7b070022b..952bfdbf8 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.25.37 h1:gBtB/F3dophWpsUQKN/Kni+JzYEH2mGHF4hWNtfED1w= +github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= @@ -130,6 +132,7 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= @@ -272,6 +275,7 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425222832-ad9eeb80039a h1:jd4PGQGmrzmDZANUzIol3eClsCB/Jp5GmpGWMhi6hnY= golang.org/x/tools v0.0.0-20190425222832-ad9eeb80039a/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= diff --git a/mining/simulator/addresslist.go b/mining/simulator/addresslist.go index e71744b4f..43fad70de 100644 --- a/mining/simulator/addresslist.go +++ b/mining/simulator/addresslist.go @@ -2,10 +2,53 @@ package main import ( "bufio" + "fmt" "os" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/daglabs/btcd/dagconfig" + "github.com/pkg/errors" ) +const instanceStateCodeActive = "16" + func getAddressList(cfg *config) ([]string, error) { + if cfg.AddressListPath != "" { + return getAddressListFromPath(cfg) + } + return getAddressListFromAWS(cfg) +} + +func getAddressListFromAWS(cfg *config) ([]string, error) { + log.Infof("Getting hosts list for autoscaling group %s", cfg.AutoScalingGroup) + sess := session.Must(session.NewSession(&aws.Config{Region: aws.String(cfg.Region)})) + ec2Client := ec2.New(sess) + instances, err := ec2Client.DescribeInstances(&ec2.DescribeInstancesInput{ + Filters: []*ec2.Filter{ + &ec2.Filter{Name: aws.String("tag:aws:autoscaling:groupName"), Values: []*string{&cfg.AutoScalingGroup}}, + &ec2.Filter{Name: aws.String("instance-state-code"), Values: []*string{aws.String(instanceStateCodeActive)}}, + }, + }) + if err != nil { + return nil, errors.Wrap(err, "Error describing instances") + } + + addressList := []string{} + for _, reservation := range instances.Reservations { + for _, instance := range reservation.Instances { + if instance.PrivateDnsName == nil { + continue + } + addressList = append(addressList, fmt.Sprintf("%s:%s", *instance.PrivateDnsName, dagconfig.DevNetParams.RPCPort)) + } + } + + return addressList, nil +} + +func getAddressListFromPath(cfg *config) ([]string, error) { file, err := os.Open(cfg.AddressListPath) if err != nil { return nil, err diff --git a/mining/simulator/config.go b/mining/simulator/config.go index 179f09859..65827307f 100644 --- a/mining/simulator/config.go +++ b/mining/simulator/config.go @@ -1,9 +1,10 @@ package main import ( + "path/filepath" + "github.com/daglabs/btcd/util" "github.com/pkg/errors" - "path/filepath" "github.com/jessevdk/go-flags" ) @@ -21,10 +22,12 @@ var ( ) type config struct { - AddressListPath string `long:"addresslist" description:"Path to a list of nodes' JSON-RPC endpoints" required:"true"` - CertificatePath string `long:"cert" description:"Path to certificate accepted by JSON-RPC endpoint"` - DisableTLS bool `long:"notls" description:"Disable TLS"` - Verbose bool `long:"verbose" short:"v" description:"Enable logging of RPC requests"` + AutoScalingGroup string `long:"autoscaling" description:"AWS AutoScalingGroup to use for address list"` + Region string `long:"region" description:"AWS region to use for address list"` + AddressListPath string `long:"addresslist" description:"Path to a list of nodes' JSON-RPC endpoints"` + CertificatePath string `long:"cert" description:"Path to certificate accepted by JSON-RPC endpoint"` + DisableTLS bool `long:"notls" description:"Disable TLS"` + Verbose bool `long:"verbose" short:"v" description:"Enable logging of RPC requests"` } func parseConfig() (*config, error) { @@ -44,6 +47,18 @@ func parseConfig() (*config, error) { return nil, errors.New("--cert should be omitted if --notls is used") } + if (cfg.AutoScalingGroup == "" || cfg.Region == "") && cfg.AddressListPath == "" { + return nil, errors.New("Either (--autoscaling and --region) or --addresslist must be specified") + } + + if (cfg.AutoScalingGroup != "" || cfg.Region != "") && cfg.AddressListPath != "" { + return nil, errors.New("Both (--autoscaling and --region) and --addresslist can't be specified at the same time") + } + + if cfg.AutoScalingGroup != "" && cfg.Region == "" { + return nil, errors.New("If --autoscaling is specified --region must be specified as well") + } + initLog(defaultLogFile, defaultErrLogFile) return cfg, nil diff --git a/mining/simulator/conn_manager.go b/mining/simulator/conn_manager.go new file mode 100644 index 000000000..f210055f7 --- /dev/null +++ b/mining/simulator/conn_manager.go @@ -0,0 +1,149 @@ +package main + +import ( + "io/ioutil" + "time" + + "github.com/daglabs/btcd/rpcclient" + "github.com/pkg/errors" +) + +type connectionManager struct { + addressList []string + cert []byte + clients []*simulatorClient + cfg *config + disconnectChan chan struct{} +} + +func newConnectionManager(cfg *config) (*connectionManager, error) { + connManager := &connectionManager{ + cfg: cfg, + } + var err error + + connManager.addressList, err = getAddressList(cfg) + if err != nil { + return nil, err + } + + connManager.cert, err = readCert(cfg) + if err != nil { + return nil, err + } + + connManager.clients, err = connectToServers(connManager.addressList, connManager.cert) + if err != nil { + return nil, err + } + + if cfg.AutoScalingGroup != "" { + connManager.disconnectChan = make(chan struct{}) + spawn(func() { connManager.refreshAddressesLoop() }) + } + + return connManager, nil +} + +func connectToServer(address string, cert []byte) (*simulatorClient, error) { + connCfg := &rpcclient.ConnConfig{ + Host: address, + Endpoint: "ws", + User: "user", + Pass: "pass", + DisableTLS: cert == nil, + RequestTimeout: time.Second * 10, + Certificates: cert, + } + + client, err := newSimulatorClient(address, connCfg) + if err != nil { + return nil, err + } + + log.Infof("Connected to server %s", address) + + return client, nil +} + +func connectToServers(addressList []string, cert []byte) ([]*simulatorClient, error) { + clients := make([]*simulatorClient, 0, len(addressList)) + + for _, address := range addressList { + client, err := connectToServer(address, cert) + if err != nil { + return nil, err + } + clients = append(clients, client) + } + + return clients, nil +} + +func readCert(cfg *config) ([]byte, error) { + if cfg.DisableTLS { + return nil, nil + } + + cert, err := ioutil.ReadFile(cfg.CertificatePath) + if err != nil { + return nil, errors.Errorf("Error reading certificates file: %s", err) + } + + return cert, nil +} + +func (cm *connectionManager) close() { + if cm.disconnectChan != nil { + cm.disconnectChan <- struct{}{} + } + for _, client := range cm.clients { + client.Disconnect() + } +} + +const refreshAddressInterval = time.Minute * 10 + +func (cm *connectionManager) refreshAddressesLoop() { + for { + select { + case <-time.After(refreshAddressInterval): + err := cm.refreshAddresses() + if err != nil { + panic(err) + } + case <-cm.disconnectChan: + return + } + } +} + +func (cm *connectionManager) refreshAddresses() error { + newAddressList, err := getAddressList(cm.cfg) + if err != nil { + return err + } + + if len(newAddressList) == len(cm.addressList) { + return nil + } + +outerLoop: + for _, newAddress := range newAddressList { + for _, oldAddress := range cm.addressList { + if newAddress == oldAddress { + continue outerLoop + } + } + + client, err := connectToServer(newAddress, cm.cert) + if err != nil { + return err + } + cm.clients = append(cm.clients, client) + } + + cm.addressList = newAddressList + + return nil +} diff --git a/mining/simulator/connect.go b/mining/simulator/connect.go deleted file mode 100644 index ee9d51359..000000000 --- a/mining/simulator/connect.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "github.com/pkg/errors" - "io/ioutil" - "time" - - "github.com/daglabs/btcd/rpcclient" -) - -func connectToServers(cfg *config, addressList []string) ([]*simulatorClient, error) { - clients := make([]*simulatorClient, len(addressList)) - - var cert []byte - if !cfg.DisableTLS { - var err error - cert, err = ioutil.ReadFile(cfg.CertificatePath) - if err != nil { - return nil, errors.Errorf("Error reading certificates file: %s", err) - } - } - - for i, address := range addressList { - connCfg := &rpcclient.ConnConfig{ - Host: address, - Endpoint: "ws", - User: "user", - Pass: "pass", - DisableTLS: cfg.DisableTLS, - RequestTimeout: time.Second * 10, - } - - if !cfg.DisableTLS { - connCfg.Certificates = cert - } - - var err error - clients[i], err = newSimulatorClient(address, connCfg) - if err != nil { - return nil, err - } - - log.Infof("Connected to server %s", address) - } - - return clients, nil -} diff --git a/mining/simulator/main.go b/mining/simulator/main.go index 3bb5923f1..fe48ed61a 100644 --- a/mining/simulator/main.go +++ b/mining/simulator/main.go @@ -2,9 +2,10 @@ package main import ( "fmt" - "github.com/pkg/errors" "os" + "github.com/pkg/errors" + "github.com/daglabs/btcd/signal" "github.com/daglabs/btcd/util/panics" ) @@ -21,19 +22,14 @@ func main() { enableRPCLogging() } - addressList, err := getAddressList(cfg) + connManager, err := newConnectionManager(cfg) if err != nil { - panic(errors.Errorf("Couldn't load address list: %s", err)) + panic(errors.Errorf("Error initializing connection manager: %s", err)) } - - clients, err := connectToServers(cfg, addressList) - if err != nil { - panic(errors.Errorf("Error connecting to servers: %s", err)) - } - defer disconnect(clients) + defer connManager.close() spawn(func() { - err = mineLoop(clients) + err = mineLoop(connManager) if err != nil { panic(errors.Errorf("Error in main loop: %s", err)) } diff --git a/mining/simulator/mineloop.go b/mining/simulator/mineloop.go index 2e415c0e6..eac0feb4a 100644 --- a/mining/simulator/mineloop.go +++ b/mining/simulator/mineloop.go @@ -3,13 +3,14 @@ package main import ( "encoding/hex" nativeerrors "errors" - "github.com/daglabs/btcd/rpcclient" - "github.com/pkg/errors" "math/rand" "strconv" "strings" "time" + "github.com/daglabs/btcd/rpcclient" + "github.com/pkg/errors" + "github.com/daglabs/btcd/blockdag" "github.com/daglabs/btcd/btcjson" "github.com/daglabs/btcd/util" @@ -170,7 +171,7 @@ func getRandomClient(clients []*simulatorClient) *simulatorClient { return clients[random.Int63n(clientsCount)] } -func mineLoop(clients []*simulatorClient) error { +func mineLoop(connManager *connectionManager) error { errChan := make(chan error) templateStopChan := make(chan struct{}) @@ -178,7 +179,7 @@ func mineLoop(clients []*simulatorClient) error { spawn(func() { for { foundBlock := make(chan *util.Block) - currentClient := getRandomClient(clients) + currentClient := getRandomClient(connManager.clients) currentClient.notifyForNewBlocks = true log.Infof("Next block will be mined by: %s", currentClient.Host()) mineNextBlock(currentClient, foundBlock, templateStopChan, errChan)