github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/miningpool/database.go (about) 1 package pool 2 3 import ( 4 //"database/sql" 5 "github.com/go-redis/redis" 6 //"context" 7 "errors" 8 "fmt" 9 "strconv" 10 "time" 11 12 "SiaPrime/types" 13 ) 14 15 var ( 16 // ErrDuplicateUserInDifferentCoin is an error when a address used in 17 // different coin 18 //ErrDuplicateUserInDifferentCoin = errors.New("duplicate user in different coin, you need use a different address") 19 // ErrNoUsernameInDatabase is an error when can't find a username in db 20 ErrNoUsernameInDatabase = errors.New("user is not found in db") 21 // ErrCreateClient is an error when can't create a new client 22 ErrCreateClient = errors.New("Error when creating a new client") 23 ErrQueryTimeout = errors.New("DB query timeout") 24 ErrConnectDB = errors.New("error in connecting redis") 25 DB = []string{"accounts", "workers", "shares", "blocks"} 26 ) 27 28 const ( 29 sqlReconnectRetry = 6 30 sqlRetryDelay = 10 31 sqlQueryTimeout = 5 32 redisExpireTime = 48 * 60 * 60 * time.Second 33 ) 34 35 func (p *Pool) newDbConnection() error { 36 dbc := *p.InternalSettings().PoolRedisConnection 37 p.dbConnectionMu.Lock() 38 defer p.dbConnectionMu.Unlock() 39 var err error 40 41 i := 0 42 for _, conn := range p.redisdb { 43 _, err = conn.Ping().Result() 44 if err == nil { 45 i++ 46 } 47 } 48 49 if i == len(DB) { 50 return nil 51 } 52 53 for _, s := range DB { 54 hosts := []string{} 55 for _, host := range dbc["hosts"].([]string) { 56 hosts = append(hosts, fmt.Sprintf("%s:%d", host, dbc["tables"].(map[string]interface{})[s].(int))) 57 } 58 59 fmt.Printf("try to connect redis: %s\n", s) 60 p.redisdb[s] = redis.NewClusterClient(&redis.ClusterOptions{ 61 Addrs: hosts, 62 Password: dbc["pass"].(string), 63 }) 64 if err != nil { 65 fmt.Println(err) 66 return err 67 } 68 _, err = p.redisdb[s].Ping().Result() 69 if err != nil { 70 fmt.Println(err) 71 return err 72 } 73 fmt.Println("Connection successful.") 74 } 75 76 i = 0 77 for _, conn := range p.redisdb { 78 _, err = conn.Ping().Result() 79 if err == nil { 80 i++ 81 } 82 } 83 84 if i == len(DB) { 85 return nil 86 } 87 88 return ErrConnectDB 89 } 90 91 func (p *Pool) closeAllDB() { 92 for _, conn := range p.redisdb { 93 conn.Close() 94 } 95 } 96 97 // AddClientDB add user into accounts 98 func (p *Pool) AddClientDB(c *Client) error { 99 id := p.newStratumID()() 100 err := p.redisdb["accounts"].Set(c.Name(), id, 0).Err() 101 if err != nil { 102 return err 103 } 104 105 p.dblog.Printf("User %s account id is %d\n", c.Name(), id) 106 c.SetID(id) 107 108 return nil 109 } 110 111 // addWorkerDB inserts info to workers 112 func (c *Client) addWorkerDB(w *Worker) error { 113 id := c.Pool().newStratumID()() 114 err := c.Pool().redisdb["workers"].HMSet( 115 fmt.Sprintf("%d.%d", c.GetID(), id), 116 map[string]interface{} { 117 "wallet": c.Name(), 118 "worker": w.Name(), 119 "time": time.Now().Unix(), 120 "pid": c.pool.InternalSettings().PoolID, 121 "version": w.Session().clientVersion, 122 "ip": w.Session().remoteAddr, 123 }).Err() 124 err = c.Pool().redisdb["workers"].Persist( 125 fmt.Sprintf("%d.%d", c.GetID(), id)).Err() 126 if err != nil { 127 return err 128 } 129 w.SetID(id) 130 return nil 131 } 132 133 // FindClientDB find user in accounts 134 func (p *Pool) FindClientDB(name string) (*Client, error) { 135 var clientID uint64 136 var err error 137 c := p.Client(name) 138 // if it's in memory, just return a pointer to the copy in memory 139 if c != nil { 140 return c, nil 141 } 142 143 id, err := p.redisdb["accounts"].Get(name).Result() 144 if err == redis.Nil { 145 return nil, ErrNoUsernameInDatabase 146 } else if err != nil { 147 fmt.Println(err) 148 return nil, err 149 } 150 151 clientID, err = strconv.ParseUint(id, 10, 64) 152 if err != nil { 153 return nil, err 154 } 155 // client was in database but not in memory - 156 // find workers and connect them to the in memory copy 157 c, err = newClient(p, name) 158 if err != nil { 159 p.dblog.Printf("Error when creating a new client %s: %s\n", name, err) 160 return nil, ErrCreateClient 161 } 162 var wallet types.UnlockHash 163 wallet.LoadString(name) 164 c.SetWallet(wallet) 165 c.SetID(clientID) 166 167 return c, nil 168 } 169 170 func (w *Worker) deleteWorkerRecord() error { 171 err := w.Parent().Pool().redisdb["workers"].Expire( 172 fmt.Sprintf("%d.%d", w.Parent().GetID(), w.GetID()), 173 redisExpireTime).Err() 174 if err != nil { 175 w.Parent().Pool().dblog.Printf("Error setting redis worker expire: %s\n", err) 176 return err 177 } 178 return nil 179 } 180 181 func (p *Pool) DeleteAllWorkerRecords() error { 182 conn := p.redisdb["workers"] 183 var cursor uint64 184 match := "*" 185 count := int64(10) 186 for { 187 var keys []string 188 var err error 189 keys, cursor, err = conn.Scan(cursor, match, count).Result() 190 if err != nil { 191 fmt.Println(err) 192 return err 193 } 194 for _, key := range keys { 195 if conn.TTL(key).Val() < 0 { 196 err = conn.Expire(key, redisExpireTime).Err() 197 if err != nil { 198 return err 199 } 200 } 201 } 202 if cursor == 0 { 203 break 204 } 205 } 206 return nil 207 } 208 // DeleteAllWorkerRecords deletes all worker records associated with a pool. 209 // This should be used on pool startup and shutdown to ensure the database 210 // is clean and isn't storing any worker records for non-connected workers. 211 /* 212 func (p *Pool) DeleteAllWorkerRecords() error { 213 err := p.redisdb["workers"].FlushDB().Err() 214 if err != nil { 215 p.dblog.Printf("Error deleting records: %s\n", err) 216 return err 217 } 218 return nil 219 }*/ 220 221 // addFoundBlock add founded block to yiimp blocks table 222 func (w *Worker) addFoundBlock(b *types.Block) error { 223 pool := w.Parent().Pool() 224 bh := pool.persist.GetBlockHeight() 225 //w.log.Printf("New block to mine on %d\n", uint64(bh)+1) 226 // reward := b.CalculateSubsidy(bh).String() 227 pool.blockFoundMu.Lock() 228 defer pool.blockFoundMu.Unlock() 229 timeStamp := time.Now().Unix() 230 currentTarget, _ := pool.cs.ChildTarget(b.ID()) 231 difficulty, _ := currentTarget.Difficulty().Uint64() // TODO: maybe should use parent ChildTarget 232 exists := pool.redisdb["blocks"].Exists(strconv.FormatUint(uint64(bh), 10)).Val() 233 if exists == 1 { 234 pool.dblog.Printf("orphan block %s found, at height %d\n", b.ID().String(), uint64(bh)) 235 return nil 236 } 237 err := pool.redisdb["blocks"].HMSet( 238 strconv.FormatUint(uint64(bh), 10), 239 map[string]interface{} { 240 "blockhash": b.ID().String(), 241 "user": w.Parent().Name(), 242 "worker": w.Name(), 243 "category": "new", 244 "difficulty": difficulty, 245 "time": timeStamp, 246 }).Err() 247 if err != nil { 248 return err 249 } 250 return nil 251 } 252 253 // SaveShift periodically saves the shares for a given worker to the db 254 func (s *Shift) SaveShift() error { 255 if len(s.Shares()) == 0 { 256 return nil 257 } 258 259 worker := s.worker 260 client := worker.Parent() 261 redisdb := client.Pool().redisdb["shares"] 262 for _, share := range s.Shares() { 263 err := redisdb.HMSet( 264 fmt.Sprintf("%d.%d.%d.%d", client.GetID(), worker.GetID(), share.height, share.time.Unix()), 265 map[string]interface{} { 266 "valid": share.valid, 267 "difficulty": share.difficulty, 268 "reward": share.reward, 269 "block_difficulty": share.blockDifficulty, 270 "share_reward": share.shareReward, 271 "share_diff": share.shareDifficulty, 272 }).Err() 273 if err != nil { 274 client.Pool().dblog.Println(err) 275 return err 276 } 277 } 278 return nil 279 }