gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/skylink.go (about) 1 package renter 2 3 import ( 4 "context" 5 "sync" 6 "time" 7 8 "gitlab.com/NebulousLabs/errors" 9 "gitlab.com/SkynetLabs/skyd/build" 10 "gitlab.com/SkynetLabs/skyd/skymodules" 11 "gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem" 12 "go.sia.tech/siad/crypto" 13 ) 14 15 // skylinkManager manages skylink requests 16 type skylinkManager struct { 17 // pruneTimeThreshold is the time threshold for pruning unpin requests. 18 pruneTimeThreshold time.Time 19 20 // unpinRequests are requests to unpin a skylink. It is a map of 21 // Skylink.String() to a time.Time by when the skylink should be fully 22 // unpinned. It is a map of strings instead of skylinks because the FileNode 23 // contain a slice of strings and not a slice of Skylinks. 24 unpinRequests map[string]time.Time 25 mu sync.Mutex 26 } 27 28 // newSkylinkManager returns a newly initialized skylinkManager 29 func newSkylinkManager() *skylinkManager { 30 return &skylinkManager{ 31 unpinRequests: make(map[string]time.Time), 32 } 33 } 34 35 // callIsUnpinned returns whether or not a FileNode has be requested to be 36 // unpinned. 37 func (sm *skylinkManager) callIsUnpinned(fn *filesystem.FileNode) bool { 38 sm.mu.Lock() 39 defer sm.mu.Unlock() 40 41 // Check all the skylinks associated with the FileNode. If any have a pending 42 // unpin request then we return true. 43 for _, skylink := range fn.Metadata().Skylinks { 44 // Check if skylink has a pending unpin request. 45 if _, ok := sm.unpinRequests[skylink]; ok { 46 return true 47 } 48 } 49 return false 50 } 51 52 // callPruneUnpinRequests will prune the skylinkManager's old unpinRequests 53 func (sm *skylinkManager) callPruneUnpinRequests() { 54 sm.mu.Lock() 55 defer sm.mu.Unlock() 56 57 // Iterate over the unpinRequests and check for requests that are in the 58 // past when compared to the pruneTime 59 for sl, t := range sm.unpinRequests { 60 // If the unpin request's time is in the past we can remove it from the 61 // list. 62 if t.Before(sm.pruneTimeThreshold) { 63 delete(sm.unpinRequests, sl) 64 } 65 } 66 } 67 68 // callUpdatePruneTimeThreshold updates the skylinkManager's pruneTimeThreshold 69 func (sm *skylinkManager) callUpdatePruneTimeThreshold(t time.Time) { 70 sm.mu.Lock() 71 defer sm.mu.Unlock() 72 sm.pruneTimeThreshold = t 73 } 74 75 // managedAddUnpinRequest adds an unpin request to the skylinkManager 76 func (sm *skylinkManager) managedAddUnpinRequest(skylink skymodules.Skylink) { 77 sm.mu.Lock() 78 defer sm.mu.Unlock() 79 80 // Grab the string 81 skylinkStr := skylink.String() 82 // Check if the request was already submitted. If so, return as to not reset 83 // the time. 84 _, ok := sm.unpinRequests[skylinkStr] 85 if ok { 86 // unpin request already submitted 87 return 88 } 89 90 // Add the unpin request and set the time in the future by twice the target 91 // health check interval. 92 sm.unpinRequests[skylinkStr] = time.Now().Add(TargetHealthCheckFrequency * 2) 93 } 94 95 // UnpinSkylink unpins a skylink from the renter by removing the underlying 96 // siafile. 97 // 98 // NOTE: Since the SiaPath is not stored in the SkyfileLayout or the 99 // SkyfileMetadata, we have to iterate over the entire filesystem to try and 100 // find the filenode(s) that contain the skylink. We handle this via the bubble 101 // code and the Renter's skylinkManager. 102 func (r *Renter) UnpinSkylink(skylink skymodules.Skylink) error { 103 err := r.tg.Add() 104 if err != nil { 105 return err 106 } 107 defer r.tg.Done() 108 109 // Check if link is v2. 110 if skylink.IsSkylinkV2() { 111 return errors.New("can't unpin version 2 skylink") 112 } 113 114 // Check if skylink is blocked. If it is we can return early since the bubble 115 // code will handle deletion of blocked files. 116 err = r.managedHandleIsBlockedCheck(r.tg.StopCtx(), skylink, skymodules.SiaPath{}) 117 if err != nil { 118 return err 119 } 120 121 // Check for dependency injection 122 if r.staticDeps.Disrupt("SkipUnpinRequest") { 123 return nil 124 } 125 126 // Add the unpin request 127 r.staticSkylinkManager.managedAddUnpinRequest(skylink) 128 return nil 129 } 130 131 // managedBlocklistHash returns the hash to be used in the blocklist 132 func (r *Renter) managedBlocklistHash(ctx context.Context, sl skymodules.Skylink, ignoreCutoff bool) (crypto.Hash, error) { 133 // We want to return the hash of the merkleroot of the V1 skylink. This 134 // means for V2 skylinks we need to resolve it first. 135 switch { 136 case sl.IsSkylinkV1(): 137 return crypto.HashObject(sl.MerkleRoot()), nil 138 case sl.IsSkylinkV2(): 139 // NOTE: We don't want to check the blocklist while the V2 link 140 // is being resolved. This is so a link that is already on the 141 // block list could be added again in a large user generated 142 // list. 143 slv1, _, err := r.managedTryResolveSkylinkV2(ctx, sl, false, false) 144 if err != nil { 145 return crypto.Hash{}, errors.AddContext(err, "unable to resolve V2 skylink") 146 } 147 return crypto.HashObject(slv1.MerkleRoot()), nil 148 default: 149 build.Critical(ErrInvalidSkylinkVersion) 150 } 151 return crypto.Hash{}, ErrInvalidSkylinkVersion 152 } 153 154 // managedHandleIsBlockedCheck handles checking if a skylink is blocked. It will 155 // also handle the deletion of a blocked file if necessary. This method can be 156 // used for both V1 and V2 skylinks. 157 func (r *Renter) managedHandleIsBlockedCheck(ctx context.Context, sl skymodules.Skylink, siaPath skymodules.SiaPath) error { 158 // Convert skylink to blocklist hash 159 hash, err := r.managedBlocklistHash(ctx, sl, false) 160 if err != nil { 161 return errors.AddContext(err, "unable to get blocklist hash") 162 } 163 // Check the Skynet Blocklist 164 shouldDelete, isBlocked := r.staticSkynetBlocklist.IsHashBlocked(hash) 165 if !isBlocked { 166 return nil 167 } 168 // Check if the skyfile should be deleted 169 if shouldDelete && !siaPath.IsEmpty() { 170 // Skylink is blocked and the data should be deleted, try and 171 // delete file 172 deleteErr := r.DeleteFile(siaPath) 173 // Don't bother logging an error if the file doesn't exist 174 if deleteErr != nil && !errors.Contains(deleteErr, filesystem.ErrNotExist) { 175 r.staticLog.Println("WARN: unable to delete blocked skyfile at", siaPath.String()) 176 } 177 } 178 // Return that the Skylink is blocked 179 return ErrSkylinkBlocked 180 } 181 182 // managedParseBlocklistHashes parses the input hash string slice and returns 183 // the appropriate hash to be added to the blocklist. A slice of invalid inputs 184 // is returned for every input that could not get properly parsed. 185 func (r *Renter) managedParseBlocklistHashes(ctx context.Context, hashStrs []string, isHash, ignoreCutoff bool) ([]crypto.Hash, []skymodules.SkynetBlocklistInvalidInput) { 186 var invalids []skymodules.SkynetBlocklistInvalidInput 187 var hashes []crypto.Hash 188 for _, paramStr := range hashStrs { 189 var hash crypto.Hash 190 // Convert Hash 191 if isHash { 192 err := hash.LoadString(paramStr) 193 if err != nil { 194 invalids = append(invalids, skymodules.SkynetBlocklistInvalidInput{ 195 Input: paramStr, 196 Error: err.Error(), 197 }) 198 continue 199 } 200 } else { 201 // Convert Skylink 202 var skylink skymodules.Skylink 203 err := skylink.LoadString(paramStr) 204 if err != nil { 205 invalids = append(invalids, skymodules.SkynetBlocklistInvalidInput{ 206 Input: paramStr, 207 Error: err.Error(), 208 }) 209 continue 210 } 211 hash, err = r.managedBlocklistHash(ctx, skylink, ignoreCutoff) 212 if err != nil { 213 invalids = append(invalids, skymodules.SkynetBlocklistInvalidInput{ 214 Input: paramStr, 215 Error: err.Error(), 216 }) 217 continue 218 } 219 } 220 hashes = append(hashes, hash) 221 } 222 return hashes, invalids 223 }