gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/skynetblocklist/skynetblocklist.go (about)

     1  package skynetblocklist
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"sync"
     8  	"time"
     9  
    10  	"gitlab.com/NebulousLabs/encoding"
    11  	"gitlab.com/NebulousLabs/errors"
    12  	"gitlab.com/SkynetLabs/skyd/build"
    13  	"gitlab.com/SkynetLabs/skyd/skymodules"
    14  	"go.sia.tech/siad/crypto"
    15  	"go.sia.tech/siad/persist"
    16  	"go.sia.tech/siad/types"
    17  )
    18  
    19  const (
    20  	// persistFile is the name of the persist file
    21  	persistFile string = "skynetblocklist.dat"
    22  
    23  	// persistSize is the size of a persisted merkleroot in the blocklist. It is
    24  	// the length of `merkleroot` plus the int64 probationary period plus
    25  	// the `listed` flag (32 + 8 + 1).
    26  	persistSize uint64 = 41
    27  )
    28  
    29  var (
    30  	// DefaultProbationaryPeriod is the default length in seconds of the
    31  	// blocklist probationary period. During this time, skylinks will be
    32  	// blocked but not deleted
    33  	//
    34  	// NOTE: The default for production is 90 days as this is a legal
    35  	// requirement in the USA for potentially illegal content to be
    36  	// immediately blocked, but retained for 90 days to help with
    37  	// investigations.
    38  	DefaultProbationaryPeriod = build.Select(build.Var{
    39  		Standard: int64(90 * 24 * 60 * 60), // 90 days
    40  		Dev:      int64(24 * 60 * 60),      // 1 day
    41  		Testing:  int64(60),
    42  	}).(int64)
    43  
    44  	// metadataHeader is the header of the metadata for the persist file
    45  	metadataHeader = types.NewSpecifier("SkynetBlocklist\n")
    46  
    47  	// metadataVersion is the version of the persistence file
    48  	metadataVersion = metadataVersionV1510
    49  )
    50  
    51  type (
    52  	// SkynetBlocklist manages a set of blocked skylinks by tracking the
    53  	// merkleroots and persists the list to disk.
    54  	SkynetBlocklist struct {
    55  		staticAop *persist.AppendOnlyPersist
    56  
    57  		// hashes is a map of hashed blocked merkleroots to the uinx
    58  		// timestamp of the end of their probationary period. During the
    59  		// probationary period, the skylinks are blocked but the
    60  		// underlying content is not deleted. This allows portal
    61  		// operations time to validate blocklist requests to protect
    62  		// against malicious blocklisting.
    63  		hashes map[crypto.Hash]int64
    64  
    65  		mu sync.Mutex
    66  	}
    67  
    68  	// persistEntry contains a hash and whether it should be listed as being in
    69  	// the current blocklist.
    70  	persistEntry struct {
    71  		Hash                  crypto.Hash
    72  		ProbationaryPeriodEnd int64
    73  		Listed                bool
    74  	}
    75  )
    76  
    77  // New returns an initialized SkynetBlocklist.
    78  func New(persistDir string) (*SkynetBlocklist, error) {
    79  	// Load the persistence of the blocklist.
    80  	aop, reader, err := loadPersist(persistDir)
    81  	if err != nil {
    82  		return nil, errors.AddContext(err, "unable to load the skynet blocklist persistence")
    83  	}
    84  
    85  	sb := &SkynetBlocklist{
    86  		staticAop: aop,
    87  	}
    88  	hashes, err := unmarshalObjects(reader, metadataVersion)
    89  	if err != nil {
    90  		err = errors.Compose(err, aop.Close())
    91  		return nil, errors.AddContext(err, "unable to unmarshal persist objects")
    92  	}
    93  	sb.hashes = hashes
    94  
    95  	return sb, nil
    96  }
    97  
    98  // Blocklist returns the hashes of the merkleroots that are blocked
    99  func (sb *SkynetBlocklist) Blocklist() []crypto.Hash {
   100  	sb.mu.Lock()
   101  	defer sb.mu.Unlock()
   102  
   103  	var blocklist []crypto.Hash
   104  	for hash := range sb.hashes {
   105  		blocklist = append(blocklist, hash)
   106  	}
   107  	return blocklist
   108  }
   109  
   110  // Close closes and frees associated resources.
   111  func (sb *SkynetBlocklist) Close() error {
   112  	return sb.staticAop.Close()
   113  }
   114  
   115  // IsBlocked indicates if a skylink is currently blocked and if it should be
   116  // deleted.
   117  func (sb *SkynetBlocklist) IsBlocked(skylink skymodules.Skylink) (shouldDelete, isBlocked bool) {
   118  	if !skylink.IsSkylinkV1() {
   119  		build.Critical("IsBlocked requires V1 skylink")
   120  		return false, false
   121  	}
   122  	hash := crypto.HashObject(skylink.MerkleRoot())
   123  	return sb.IsHashBlocked(hash)
   124  }
   125  
   126  // IsHashBlocked indicates if a hash is currently blocked and if it should be
   127  // deleted.
   128  func (sb *SkynetBlocklist) IsHashBlocked(hash crypto.Hash) (shouldDelete, isBlocked bool) {
   129  	sb.mu.Lock()
   130  	defer sb.mu.Unlock()
   131  	probationaryPeriodEnd, ok := sb.hashes[hash]
   132  	// If the hash exists it is blocked, and if the probationaryPeriod is in
   133  	// the past we should delete the data.
   134  	return time.Now().Unix() >= probationaryPeriodEnd, ok
   135  }
   136  
   137  // UpdateBlocklist updates the list of skylinks that are blocked.
   138  func (sb *SkynetBlocklist) UpdateBlocklist(additions, removals []crypto.Hash, probationaryPeriod int64) error {
   139  	sb.mu.Lock()
   140  	defer sb.mu.Unlock()
   141  
   142  	buf, err := sb.marshalObjects(additions, removals, probationaryPeriod)
   143  	if err != nil {
   144  		return errors.AddContext(err, fmt.Sprintf("unable to update skynet blocklist persistence at '%v'", sb.staticAop.FilePath()))
   145  	}
   146  	_, err = sb.staticAop.Write(buf.Bytes())
   147  	return errors.AddContext(err, fmt.Sprintf("unable to update skynet blocklist persistence at '%v'", sb.staticAop.FilePath()))
   148  }
   149  
   150  // marshalObjects marshals the given objects into a byte buffer.
   151  func (sb *SkynetBlocklist) marshalObjects(additions, removals []crypto.Hash, probationaryPeriod int64) (bytes.Buffer, error) {
   152  	// Create buffer for encoder
   153  	var buf bytes.Buffer
   154  	// Create and encode the persist links
   155  	listed := true
   156  	for _, hash := range additions {
   157  		probationaryPeriodEnd := time.Now().Unix() + probationaryPeriod
   158  		// Check if the hash is already blocked
   159  		if _, ok := sb.hashes[hash]; ok {
   160  			// Update the probationaryPeriod
   161  			sb.hashes[hash] = probationaryPeriodEnd
   162  			continue
   163  		}
   164  
   165  		// Add hash to map
   166  		sb.hashes[hash] = probationaryPeriodEnd
   167  
   168  		// Marshal the update
   169  		pe := persistEntry{hash, probationaryPeriodEnd, listed}
   170  		data := encoding.Marshal(pe)
   171  		_, err := buf.Write(data)
   172  		if err != nil {
   173  			return bytes.Buffer{}, errors.AddContext(err, "unable to write addition to the buffer")
   174  		}
   175  	}
   176  	listed = false
   177  	for _, hash := range removals {
   178  		// Check if the hash is already removed
   179  		if _, ok := sb.hashes[hash]; !ok {
   180  			continue
   181  		}
   182  
   183  		// Remove hash from map
   184  		delete(sb.hashes, hash)
   185  
   186  		// Marshal the update
   187  		pe := persistEntry{hash, 0, listed}
   188  		data := encoding.Marshal(pe)
   189  		_, err := buf.Write(data)
   190  		if err != nil {
   191  			return bytes.Buffer{}, errors.AddContext(err, "unable to write removal to the buffer")
   192  		}
   193  	}
   194  
   195  	return buf, nil
   196  }
   197  
   198  // unmarshalObjects unmarshals the sia encoded objects.
   199  func unmarshalObjects(reader io.Reader, version types.Specifier) (map[crypto.Hash]int64, error) {
   200  	// Version Check
   201  	switch version {
   202  	case metadataVersionV1510:
   203  	default:
   204  		return nil, errors.New("bad version")
   205  	}
   206  	blocklist := make(map[crypto.Hash]int64)
   207  	// Unmarshal blocked links one by one until EOF.
   208  	var offset uint64
   209  	for {
   210  		buf := make([]byte, persistSize)
   211  		_, err := io.ReadFull(reader, buf)
   212  		if errors.Contains(err, io.EOF) {
   213  			break
   214  		}
   215  		if err != nil {
   216  			return nil, err
   217  		}
   218  		var pe persistEntry
   219  		err = encoding.Unmarshal(buf, &pe)
   220  		if err != nil {
   221  			return nil, err
   222  		}
   223  		offset += persistSize
   224  
   225  		if !pe.Listed {
   226  			delete(blocklist, pe.Hash)
   227  			continue
   228  		}
   229  		blocklist[pe.Hash] = pe.ProbationaryPeriodEnd
   230  	}
   231  	return blocklist, nil
   232  }