github.com/decred/politeia@v1.4.0/politeiad/backendv2/tstorebe/tstorebe.go (about)

     1  // Copyright (c) 2020-2021 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package tstorebe
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/base64"
    10  	"encoding/hex"
    11  	"encoding/json"
    12  	"fmt"
    13  	"path/filepath"
    14  	"sort"
    15  	"strconv"
    16  	"sync"
    17  	"time"
    18  
    19  	"github.com/decred/dcrd/chaincfg/v3"
    20  	"github.com/decred/politeia/politeiad/api/v1/mime"
    21  	backend "github.com/decred/politeia/politeiad/backendv2"
    22  	"github.com/decred/politeia/politeiad/backendv2/tstorebe/plugins"
    23  	"github.com/decred/politeia/politeiad/backendv2/tstorebe/tstore"
    24  	"github.com/decred/politeia/util"
    25  	"github.com/subosito/gozaru"
    26  )
    27  
    28  var (
    29  	_ backend.Backend = (*tstoreBackend)(nil)
    30  )
    31  
    32  // tstoreBackend implements the backendv2 Backend interface using a tstore as
    33  // the backing data store.
    34  type tstoreBackend struct {
    35  	sync.RWMutex
    36  	appDir   string
    37  	dataDir  string
    38  	shutdown bool
    39  	tstore   *tstore.Tstore
    40  
    41  	// recordMtxs allows the backend to hold a lock on an individual
    42  	// record so that it can perform multiple read/write operations
    43  	// in a concurrent safe manner. These mutexes are lazy loaded.
    44  	recordMtxs map[string]*sync.Mutex
    45  }
    46  
    47  // isShutdown returns whether the backend is shutdown.
    48  func (t *tstoreBackend) isShutdown() bool {
    49  	t.RLock()
    50  	defer t.RUnlock()
    51  
    52  	return t.shutdown
    53  }
    54  
    55  // recordMutex returns the mutex for a record.
    56  func (t *tstoreBackend) recordMutex(token []byte) *sync.Mutex {
    57  	t.Lock()
    58  	defer t.Unlock()
    59  
    60  	ts := hex.EncodeToString(token)
    61  	m, ok := t.recordMtxs[ts]
    62  	if !ok {
    63  		// recordMtxs is lazy loaded
    64  		m = &sync.Mutex{}
    65  		t.recordMtxs[ts] = m
    66  	}
    67  
    68  	return m
    69  }
    70  
    71  // metadataStreamsVerify verifies that all provided metadata streams are sane.
    72  func metadataStreamsVerify(metadata []backend.MetadataStream) error {
    73  	// Verify metadata
    74  	md := make(map[string]map[uint32]struct{}, len(metadata))
    75  	for i, v := range metadata {
    76  		// Verify all fields are provided
    77  		switch {
    78  		case v.PluginID == "":
    79  			e := fmt.Sprintf("plugin id missing at index %v", i)
    80  			return backend.ContentError{
    81  				ErrorCode:    backend.ContentErrorMetadataStreamInvalid,
    82  				ErrorContext: e,
    83  			}
    84  		case v.StreamID == 0:
    85  			e := fmt.Sprintf("stream id missing at index %v", i)
    86  			return backend.ContentError{
    87  				ErrorCode:    backend.ContentErrorMetadataStreamInvalid,
    88  				ErrorContext: e,
    89  			}
    90  		case v.Payload == "":
    91  			e := fmt.Sprintf("payload missing on %v %v", v.PluginID, v.StreamID)
    92  			return backend.ContentError{
    93  				ErrorCode:    backend.ContentErrorMetadataStreamInvalid,
    94  				ErrorContext: e,
    95  			}
    96  		}
    97  
    98  		// Verify no duplicates
    99  		m, ok := md[v.PluginID]
   100  		if !ok {
   101  			m = make(map[uint32]struct{}, len(metadata))
   102  			md[v.PluginID] = m
   103  		}
   104  		if _, ok := m[v.StreamID]; ok {
   105  			e := fmt.Sprintf("%v %v", v.PluginID, v.StreamID)
   106  			return backend.ContentError{
   107  				ErrorCode:    backend.ContentErrorMetadataStreamDuplicate,
   108  				ErrorContext: e,
   109  			}
   110  		}
   111  
   112  		// Add to metadata list
   113  		m[v.StreamID] = struct{}{}
   114  		md[v.PluginID] = m
   115  	}
   116  
   117  	return nil
   118  }
   119  
   120  func metadataStreamsUpdate(curr, mdAppend, mdOverwrite []backend.MetadataStream) []backend.MetadataStream {
   121  	// Put current metadata into a map
   122  	md := make(map[string]backend.MetadataStream, len(curr))
   123  	for _, v := range curr {
   124  		k := v.PluginID + strconv.FormatUint(uint64(v.StreamID), 10)
   125  		md[k] = v
   126  	}
   127  
   128  	// Apply overwrites
   129  	for _, v := range mdOverwrite {
   130  		k := v.PluginID + strconv.FormatUint(uint64(v.StreamID), 10)
   131  		md[k] = v
   132  	}
   133  
   134  	// Apply appends. Its ok if an append is specified but there is no
   135  	// existing metadata for that metadata stream. In this case the
   136  	// append data will become the full metadata stream.
   137  	for _, v := range mdAppend {
   138  		k := v.PluginID + strconv.FormatUint(uint64(v.StreamID), 10)
   139  		m, ok := md[k]
   140  		if !ok {
   141  			// No existing metadata. Use append data as full metadata
   142  			// stream.
   143  			md[k] = v
   144  			continue
   145  		}
   146  
   147  		// Metadata exists. Append to it.
   148  		buf := bytes.NewBuffer([]byte(m.Payload))
   149  		buf.WriteString(v.Payload)
   150  		m.Payload = buf.String()
   151  		md[k] = m
   152  	}
   153  
   154  	// Convert metadata back to a slice
   155  	metadata := make([]backend.MetadataStream, 0, len(md))
   156  	for _, v := range md {
   157  		metadata = append(metadata, v)
   158  	}
   159  
   160  	return metadata
   161  }
   162  
   163  // filesVerify verifies that all provided files are sane.
   164  func filesVerify(files []backend.File, filesDel []string) error {
   165  	// Verify files are being updated
   166  	if len(files) == 0 && len(filesDel) == 0 {
   167  		return backend.ContentError{
   168  			ErrorCode: backend.ContentErrorFilesEmpty,
   169  		}
   170  	}
   171  
   172  	// Prevent paths
   173  	for i := range files {
   174  		if filepath.Base(files[i].Name) != files[i].Name {
   175  			e := fmt.Sprintf("%v contains a file path", files[i].Name)
   176  			return backend.ContentError{
   177  				ErrorCode:    backend.ContentErrorFileNameInvalid,
   178  				ErrorContext: e,
   179  			}
   180  		}
   181  	}
   182  	for _, v := range filesDel {
   183  		if filepath.Base(v) != v {
   184  			e := fmt.Sprintf("%v contains a file path", v)
   185  			return backend.ContentError{
   186  				ErrorCode:    backend.ContentErrorFileNameInvalid,
   187  				ErrorContext: e,
   188  			}
   189  		}
   190  	}
   191  
   192  	// Prevent duplicate filenames
   193  	fn := make(map[string]struct{}, len(files)+len(filesDel))
   194  	for i := range files {
   195  		if _, ok := fn[files[i].Name]; ok {
   196  			return backend.ContentError{
   197  				ErrorCode:    backend.ContentErrorFileNameDuplicate,
   198  				ErrorContext: files[i].Name,
   199  			}
   200  		}
   201  		fn[files[i].Name] = struct{}{}
   202  	}
   203  	for _, v := range filesDel {
   204  		if _, ok := fn[v]; ok {
   205  			return backend.ContentError{
   206  				ErrorCode:    backend.ContentErrorFileNameDuplicate,
   207  				ErrorContext: v,
   208  			}
   209  		}
   210  		fn[v] = struct{}{}
   211  	}
   212  
   213  	// Prevent bad filenames
   214  	for i := range files {
   215  		if gozaru.Sanitize(files[i].Name) != files[i].Name {
   216  			e := fmt.Sprintf("%v is not sanitized", files[i].Name)
   217  			return backend.ContentError{
   218  				ErrorCode:    backend.ContentErrorFileNameInvalid,
   219  				ErrorContext: e,
   220  			}
   221  		}
   222  
   223  		// Verify digest
   224  		d, ok := util.ConvertDigest(files[i].Digest)
   225  		if !ok {
   226  			return backend.ContentError{
   227  				ErrorCode:    backend.ContentErrorFileDigestInvalid,
   228  				ErrorContext: files[i].Name,
   229  			}
   230  		}
   231  
   232  		// Verify payload is not empty
   233  		if files[i].Payload == "" {
   234  			e := fmt.Sprintf("%v payload empty", files[i].Name)
   235  			return backend.ContentError{
   236  				ErrorCode:    backend.ContentErrorFilePayloadInvalid,
   237  				ErrorContext: e,
   238  			}
   239  		}
   240  
   241  		// Decode base64 payload
   242  		payload, err := base64.StdEncoding.DecodeString(files[i].Payload)
   243  		if err != nil {
   244  			e := fmt.Sprintf("%v invalid base64", files[i].Name)
   245  			return backend.ContentError{
   246  				ErrorCode:    backend.ContentErrorFilePayloadInvalid,
   247  				ErrorContext: e,
   248  			}
   249  		}
   250  
   251  		// Calculate payload digest
   252  		dp := util.Digest(payload)
   253  		if !bytes.Equal(d[:], dp) {
   254  			e := fmt.Sprintf("%v digest got %x, want %x",
   255  				files[i].Name, d[:], dp)
   256  			return backend.ContentError{
   257  				ErrorCode:    backend.ContentErrorFileDigestInvalid,
   258  				ErrorContext: e,
   259  			}
   260  		}
   261  
   262  		// Verify MIME
   263  		detectedMIMEType := mime.DetectMimeType(payload)
   264  		if detectedMIMEType != files[i].MIME {
   265  			e := fmt.Sprintf("%v mime got %v, want %v",
   266  				files[i].Name, files[i].MIME, detectedMIMEType)
   267  			return backend.ContentError{
   268  				ErrorCode:    backend.ContentErrorFileMIMETypeInvalid,
   269  				ErrorContext: e,
   270  			}
   271  		}
   272  
   273  		if !mime.MimeValid(files[i].MIME) {
   274  			return backend.ContentError{
   275  				ErrorCode:    backend.ContentErrorFileMIMETypeUnsupported,
   276  				ErrorContext: files[i].Name,
   277  			}
   278  		}
   279  	}
   280  
   281  	return nil
   282  }
   283  
   284  // filesUpdate updates the current files with new file adds and deletes.
   285  func filesUpdate(filesCurr, filesAdd []backend.File, filesDel []string) []backend.File {
   286  	// Put current files into a map
   287  	curr := make(map[string]backend.File, len(filesCurr)) // [filename]File
   288  	for _, v := range filesCurr {
   289  		curr[v.Name] = v
   290  	}
   291  
   292  	// Apply deletes
   293  	for _, fn := range filesDel {
   294  		_, ok := curr[fn]
   295  		if ok {
   296  			delete(curr, fn)
   297  		}
   298  	}
   299  
   300  	// Apply adds
   301  	for _, v := range filesAdd {
   302  		curr[v.Name] = v
   303  	}
   304  
   305  	// Convert back to a slice
   306  	f := make([]backend.File, 0, len(curr))
   307  	for _, v := range curr {
   308  		f = append(f, v)
   309  	}
   310  
   311  	return f
   312  }
   313  
   314  // recordMetadataNew returns a new record metadata.
   315  func recordMetadataNew(token []byte, files []backend.File, state backend.StateT, status backend.StatusT, version, iteration uint32) (*backend.RecordMetadata, error) {
   316  	digests := make([]string, 0, len(files))
   317  	for _, v := range files {
   318  		digests = append(digests, v.Digest)
   319  	}
   320  	m, err := util.MerkleRoot(digests)
   321  	if err != nil {
   322  		return nil, err
   323  	}
   324  	return &backend.RecordMetadata{
   325  		Token:     hex.EncodeToString(token),
   326  		Version:   version,
   327  		Iteration: iteration,
   328  		State:     state,
   329  		Status:    status,
   330  		Timestamp: time.Now().Unix(),
   331  		Merkle:    hex.EncodeToString(m[:]),
   332  	}, nil
   333  }
   334  
   335  // RecordNew creates a new record.
   336  //
   337  // This function satisfies the backendv2 Backend interface.
   338  func (t *tstoreBackend) RecordNew(metadata []backend.MetadataStream, files []backend.File) (*backend.Record, error) {
   339  	log.Tracef("RecordNew: %v metadata, %v files", len(metadata), len(files))
   340  
   341  	// Verify record content
   342  	err := metadataStreamsVerify(metadata)
   343  	if err != nil {
   344  		return nil, err
   345  	}
   346  	err = filesVerify(files, nil)
   347  	if err != nil {
   348  		return nil, err
   349  	}
   350  
   351  	// Call pre plugin hooks
   352  	pre := plugins.HookNewRecordPre{
   353  		Metadata: metadata,
   354  		Files:    files,
   355  	}
   356  	b, err := json.Marshal(pre)
   357  	if err != nil {
   358  		return nil, err
   359  	}
   360  	err = t.tstore.PluginHookPre(plugins.HookTypeNewRecordPre, string(b))
   361  	if err != nil {
   362  		return nil, err
   363  	}
   364  
   365  	// Create a new token
   366  	token, err := t.tstore.RecordNew()
   367  	if err != nil {
   368  		return nil, err
   369  	}
   370  
   371  	// Create record metadata
   372  	rm, err := recordMetadataNew(token, files, backend.StateUnvetted,
   373  		backend.StatusUnreviewed, 1, 1)
   374  	if err != nil {
   375  		return nil, err
   376  	}
   377  
   378  	// Save the record
   379  	err = t.tstore.RecordSave(token, *rm, metadata, files)
   380  	if err != nil {
   381  		return nil, fmt.Errorf("RecordSave: %v", err)
   382  	}
   383  
   384  	// Call post plugin hooks
   385  	post := plugins.HookNewRecordPost{
   386  		Metadata:       metadata,
   387  		Files:          files,
   388  		RecordMetadata: *rm,
   389  	}
   390  	b, err = json.Marshal(post)
   391  	if err != nil {
   392  		return nil, err
   393  	}
   394  	t.tstore.PluginHookPost(plugins.HookTypeNewRecordPost, string(b))
   395  
   396  	// Update the inventory cache
   397  	t.inventoryAdd(backend.StateUnvetted, token, backend.StatusUnreviewed)
   398  
   399  	// Get the full record to return
   400  	r, err := t.tstore.RecordLatest(token)
   401  	if err != nil {
   402  		return nil, fmt.Errorf("RecordLatest %x: %v", token, err)
   403  	}
   404  
   405  	return r, nil
   406  }
   407  
   408  // RecordEdit edits an existing record. This creates a new version of the
   409  // record.
   410  //
   411  // This function satisfies the backendv2 Backend interface.
   412  func (t *tstoreBackend) RecordEdit(token []byte, mdAppend, mdOverwrite []backend.MetadataStream, filesAdd []backend.File, filesDel []string) (*backend.Record, error) {
   413  	log.Tracef("RecordEdit: %x", token)
   414  
   415  	// Verify record contents. Send in a single metadata array to
   416  	// verify there are no dups.
   417  	allMD := append(mdAppend, mdOverwrite...)
   418  	err := metadataStreamsVerify(allMD)
   419  	if err != nil {
   420  		return nil, err
   421  	}
   422  	err = filesVerify(filesAdd, filesDel)
   423  	if err != nil {
   424  		return nil, err
   425  	}
   426  
   427  	// Verify record exists
   428  	if !t.RecordExists(token) {
   429  		return nil, backend.ErrRecordNotFound
   430  	}
   431  
   432  	// Apply the record changes and save the new version. The record
   433  	// lock needs to be held for the remainder of the function.
   434  	if t.isShutdown() {
   435  		return nil, backend.ErrShutdown
   436  	}
   437  	m := t.recordMutex(token)
   438  	m.Lock()
   439  	defer m.Unlock()
   440  
   441  	// Get existing record
   442  	r, err := t.tstore.RecordLatest(token)
   443  	if err != nil {
   444  		return nil, fmt.Errorf("RecordLatest: %v", err)
   445  	}
   446  
   447  	// Apply changes
   448  	var (
   449  		rm       = r.RecordMetadata
   450  		metadata = metadataStreamsUpdate(r.Metadata, mdAppend, mdOverwrite)
   451  		files    = filesUpdate(r.Files, filesAdd, filesDel)
   452  	)
   453  	recordMD, err := recordMetadataNew(token, files, rm.State, rm.Status,
   454  		rm.Version+1, rm.Iteration+1)
   455  	if err != nil {
   456  		return nil, err
   457  	}
   458  
   459  	// Verify that file changes are being made. The merkle root is the
   460  	// merkle root of the files. It will be the same if no file changes
   461  	// are being made.
   462  	if r.RecordMetadata.Merkle == recordMD.Merkle {
   463  		// No file changes found
   464  		return nil, backend.ErrNoRecordChanges
   465  	}
   466  
   467  	// Call pre plugin hooks
   468  	her := plugins.HookEditRecord{
   469  		Record:         *r,
   470  		RecordMetadata: *recordMD,
   471  		Metadata:       metadata,
   472  		Files:          files,
   473  	}
   474  	b, err := json.Marshal(her)
   475  	if err != nil {
   476  		return nil, err
   477  	}
   478  	err = t.tstore.PluginHookPre(plugins.HookTypeEditRecordPre, string(b))
   479  	if err != nil {
   480  		return nil, err
   481  	}
   482  
   483  	// Save record
   484  	err = t.tstore.RecordSave(token, *recordMD, metadata, files)
   485  	if err != nil {
   486  		switch err {
   487  		case backend.ErrRecordLocked:
   488  			return nil, err
   489  		default:
   490  			return nil, fmt.Errorf("RecordSave: %v", err)
   491  		}
   492  	}
   493  
   494  	// Call post plugin hooks
   495  	t.tstore.PluginHookPost(plugins.HookTypeEditRecordPost, string(b))
   496  
   497  	// Return updated record
   498  	r, err = t.tstore.RecordLatest(token)
   499  	if err != nil {
   500  		return nil, fmt.Errorf("RecordLatest: %v", err)
   501  	}
   502  
   503  	return r, nil
   504  }
   505  
   506  // RecordEditMetadata edits the metadata of a record without changing any
   507  // record files. This creates a new iteration of the record, but not a new
   508  // version of the record.
   509  //
   510  // This function satisfies the backendv2 Backend interface.
   511  func (t *tstoreBackend) RecordEditMetadata(token []byte, mdAppend, mdOverwrite []backend.MetadataStream) (*backend.Record, error) {
   512  	log.Tracef("RecordEditMetadata: %x", token)
   513  
   514  	// Verify metadata. Send in a single metadata array to verify there
   515  	// are no dups.
   516  	allMD := append(mdAppend, mdOverwrite...)
   517  	err := metadataStreamsVerify(allMD)
   518  	if err != nil {
   519  		return nil, err
   520  	}
   521  	if len(mdAppend) == 0 && len(mdOverwrite) == 0 {
   522  		return nil, backend.ErrNoRecordChanges
   523  	}
   524  
   525  	// Verify record exists
   526  	if !t.RecordExists(token) {
   527  		return nil, backend.ErrRecordNotFound
   528  	}
   529  
   530  	// Apply the record changes and save the new version. The record
   531  	// lock needs to be held for the remainder of the function.
   532  	if t.isShutdown() {
   533  		return nil, backend.ErrShutdown
   534  	}
   535  	m := t.recordMutex(token)
   536  	m.Lock()
   537  	defer m.Unlock()
   538  
   539  	// Get existing record
   540  	r, err := t.tstore.RecordLatest(token)
   541  	if err != nil {
   542  		return nil, fmt.Errorf("RecordLatest: %v", err)
   543  	}
   544  
   545  	// Apply changes. The version is not incremented for metadata only
   546  	// updates. The iteration is incremented.
   547  	var (
   548  		rm       = r.RecordMetadata
   549  		metadata = metadataStreamsUpdate(r.Metadata, mdAppend, mdOverwrite)
   550  	)
   551  	recordMD, err := recordMetadataNew(token, r.Files, rm.State, rm.Status,
   552  		rm.Version, rm.Iteration+1)
   553  	if err != nil {
   554  		return nil, err
   555  	}
   556  
   557  	// Call pre plugin hooks
   558  	hem := plugins.HookEditMetadata{
   559  		Record:   *r,
   560  		Metadata: metadata,
   561  	}
   562  	b, err := json.Marshal(hem)
   563  	if err != nil {
   564  		return nil, err
   565  	}
   566  	err = t.tstore.PluginHookPre(plugins.HookTypeEditMetadataPre, string(b))
   567  	if err != nil {
   568  		return nil, err
   569  	}
   570  
   571  	// Update metadata
   572  	err = t.tstore.RecordSave(token, *recordMD, metadata, r.Files)
   573  	if err != nil {
   574  		switch err {
   575  		case backend.ErrRecordLocked, backend.ErrNoRecordChanges:
   576  			return nil, err
   577  		default:
   578  			return nil, fmt.Errorf("RecordSave: %v", err)
   579  		}
   580  	}
   581  
   582  	// Call post plugin hooks
   583  	t.tstore.PluginHookPost(plugins.HookTypeEditMetadataPost, string(b))
   584  
   585  	// Return updated record
   586  	r, err = t.tstore.RecordLatest(token)
   587  	if err != nil {
   588  		return nil, fmt.Errorf("RecordLatest: %v", err)
   589  	}
   590  
   591  	return r, nil
   592  }
   593  
   594  var (
   595  	// statusChanges contains the allowed record status changes. If
   596  	// statusChanges[currentStatus][newStatus] exists then the status
   597  	// change is allowed.
   598  	statusChanges = map[backend.StatusT]map[backend.StatusT]struct{}{
   599  		// Unreviewed to...
   600  		backend.StatusUnreviewed: {
   601  			backend.StatusPublic:   {},
   602  			backend.StatusCensored: {},
   603  		},
   604  		// Public to...
   605  		backend.StatusPublic: {
   606  			backend.StatusCensored: {},
   607  			backend.StatusArchived: {},
   608  		},
   609  		// Statuses that do not allow any further transitions
   610  		backend.StatusCensored: {},
   611  		backend.StatusArchived: {},
   612  	}
   613  )
   614  
   615  // statusChangeIsAllowed returns whether the provided status change is allowed.
   616  func statusChangeIsAllowed(from, to backend.StatusT) bool {
   617  	allowed, ok := statusChanges[from]
   618  	if !ok {
   619  		return false
   620  	}
   621  	_, ok = allowed[to]
   622  	return ok
   623  }
   624  
   625  // setStatusPublic updates the status of a record to public.
   626  //
   627  // This function must be called WITH the record lock held.
   628  func (t *tstoreBackend) setStatusPublic(token []byte, rm backend.RecordMetadata, metadata []backend.MetadataStream, files []backend.File) error {
   629  	return t.tstore.RecordSave(token, rm, metadata, files)
   630  }
   631  
   632  // setStatusArchived updates the status of a record to archived.
   633  //
   634  // This function must be called WITH the record lock held.
   635  func (t *tstoreBackend) setStatusArchived(token []byte, rm backend.RecordMetadata, metadata []backend.MetadataStream, files []backend.File) error {
   636  	// Freeze record
   637  	err := t.tstore.RecordFreeze(token, rm, metadata, files)
   638  	if err != nil {
   639  		return fmt.Errorf("RecordFreeze: %v", err)
   640  	}
   641  
   642  	log.Debugf("Record frozen %x", token)
   643  
   644  	// Nothing else needs to be done for a archived record
   645  
   646  	return nil
   647  }
   648  
   649  // setStatusCensored updates the status of a record to censored.
   650  //
   651  // This function must be called WITH the record lock held.
   652  func (t *tstoreBackend) setStatusCensored(token []byte, rm backend.RecordMetadata, metadata []backend.MetadataStream, files []backend.File) error {
   653  	// Freeze the tree
   654  	err := t.tstore.RecordFreeze(token, rm, metadata, files)
   655  	if err != nil {
   656  		return fmt.Errorf("RecordFreeze: %v", err)
   657  	}
   658  
   659  	log.Debugf("Record frozen %x", token)
   660  
   661  	// Delete all record files
   662  	err = t.tstore.RecordDel(token)
   663  	if err != nil {
   664  		return fmt.Errorf("RecordDel: %v", err)
   665  	}
   666  
   667  	log.Debugf("Record contents deleted %x", token)
   668  
   669  	return nil
   670  }
   671  
   672  // RecordSetStatus sets the status of a record.
   673  //
   674  // This function satisfies the backendv2 Backend interface.
   675  func (t *tstoreBackend) RecordSetStatus(token []byte, status backend.StatusT, mdAppend, mdOverwrite []backend.MetadataStream) (*backend.Record, error) {
   676  	log.Tracef("RecordSetStatus: %x %v", token, status)
   677  
   678  	// Verify record exists
   679  	if !t.RecordExists(token) {
   680  		return nil, backend.ErrRecordNotFound
   681  	}
   682  
   683  	// The existing record must be pulled and updated. The record
   684  	// lock must be held for the rest of this function.
   685  	if t.isShutdown() {
   686  		return nil, backend.ErrShutdown
   687  	}
   688  	m := t.recordMutex(token)
   689  	m.Lock()
   690  	defer m.Unlock()
   691  
   692  	// Get existing record
   693  	r, err := t.tstore.RecordLatest(token)
   694  	if err != nil {
   695  		return nil, fmt.Errorf("RecordLatest: %v", err)
   696  	}
   697  	currStatus := r.RecordMetadata.Status
   698  
   699  	// Validate status change
   700  	if !statusChangeIsAllowed(currStatus, status) {
   701  		return nil, backend.StatusTransitionError{
   702  			From: currStatus,
   703  			To:   status,
   704  		}
   705  	}
   706  
   707  	// If the record is being made public the record state gets updated
   708  	// to vetted and the version and iteration are reset. Otherwise,
   709  	// the state and version remain the same while the iteration gets
   710  	// incremented to reflect the status change.
   711  	var (
   712  		state   = r.RecordMetadata.State
   713  		version = r.RecordMetadata.Version
   714  		iter    = r.RecordMetadata.Iteration + 1 // Increment for status change
   715  	)
   716  	if status == backend.StatusPublic {
   717  		state = backend.StateVetted
   718  		version = 1
   719  		iter = 1
   720  	}
   721  
   722  	// Apply changes
   723  	recordMD, err := recordMetadataNew(token, r.Files,
   724  		state, status, version, iter)
   725  	if err != nil {
   726  		return nil, err
   727  	}
   728  	metadata := metadataStreamsUpdate(r.Metadata, mdAppend, mdOverwrite)
   729  
   730  	// Call pre plugin hooks
   731  	hsrs := plugins.HookSetRecordStatus{
   732  		Record:         *r,
   733  		RecordMetadata: *recordMD,
   734  		Metadata:       metadata,
   735  	}
   736  	b, err := json.Marshal(hsrs)
   737  	if err != nil {
   738  		return nil, err
   739  	}
   740  	err = t.tstore.PluginHookPre(plugins.HookTypeSetRecordStatusPre, string(b))
   741  	if err != nil {
   742  		return nil, err
   743  	}
   744  
   745  	// Update record status
   746  	switch status {
   747  	case backend.StatusPublic:
   748  		err := t.setStatusPublic(token, *recordMD, metadata, r.Files)
   749  		if err != nil {
   750  			return nil, err
   751  		}
   752  	case backend.StatusArchived:
   753  		err := t.setStatusArchived(token, *recordMD, metadata, r.Files)
   754  		if err != nil {
   755  			return nil, err
   756  		}
   757  	case backend.StatusCensored:
   758  		err := t.setStatusCensored(token, *recordMD, metadata, r.Files)
   759  		if err != nil {
   760  			return nil, err
   761  		}
   762  	default:
   763  		// Should not happen
   764  		return nil, fmt.Errorf("unknown status %v", status)
   765  	}
   766  
   767  	log.Debugf("Status updated %x from %v (%v) to %v (%v)",
   768  		token, backend.Statuses[currStatus], currStatus,
   769  		backend.Statuses[status], status)
   770  
   771  	// Call post plugin hooks
   772  	t.tstore.PluginHookPost(plugins.HookTypeSetRecordStatusPost, string(b))
   773  
   774  	// Update inventory cache
   775  	switch status {
   776  	case backend.StatusPublic:
   777  		// The state is updated to vetted when a record is made public
   778  		t.inventoryMoveToVetted(token, status)
   779  	default:
   780  		t.inventoryUpdate(r.RecordMetadata.State, token, status)
   781  	}
   782  
   783  	// Return updated record
   784  	r, err = t.tstore.RecordLatest(token)
   785  	if err != nil {
   786  		return nil, fmt.Errorf("RecordLatest: %v", err)
   787  	}
   788  
   789  	return r, nil
   790  }
   791  
   792  // RecordExists returns whether a record exists.
   793  //
   794  // This method only returns whether a tree exists for the provided token. It's
   795  // possible for a tree to exist that does not correspond to a record in the
   796  // rare case that a tree was created but an unexpected error, such as a network
   797  // error, was encoutered prior to the record being saved to the tree. We ignore
   798  // this edge case because:
   799  //
   800  //  1. A user has no way to obtain this token unless the trillian instance has
   801  //     been opened to the public.
   802  //
   803  //  2. Even if they have the token they cannot do anything with it. Any attempt
   804  //     to read from the tree or write to the tree will return a RecordNotFound
   805  //     error.
   806  //
   807  // Pulling the leaves from the tree to see if a record has been saved to the
   808  // tree adds a large amount of overhead to this call, which should be a very
   809  // light weight. Its for this reason that we rely on the tree exists call
   810  // despite the edge case.
   811  //
   812  // This function satisfies the backendv2 Backend interface.
   813  func (t *tstoreBackend) RecordExists(token []byte) bool {
   814  	log.Tracef("RecordExists: %x", token)
   815  
   816  	return t.tstore.RecordExists(token)
   817  }
   818  
   819  // RecordTimestamps returns the timestamps for a record. If no version is
   820  // provided then timestamps for the most recent version will be returned.
   821  //
   822  // This function satisfies the backendv2 Backend interface.
   823  func (t *tstoreBackend) RecordTimestamps(token []byte, version uint32) (*backend.RecordTimestamps, error) {
   824  	log.Tracef("RecordTimestamps: %x %v", token, version)
   825  
   826  	return t.tstore.RecordTimestamps(token, version)
   827  }
   828  
   829  // Records retreives a batch of records. Individual record errors are not
   830  // returned. If the record was not found then it will not be included in the
   831  // returned map.
   832  //
   833  // This function satisfies the backendv2 Backend interface.
   834  func (t *tstoreBackend) Records(reqs []backend.RecordRequest) (map[string]backend.Record, error) {
   835  	log.Tracef("Records: %v reqs", len(reqs))
   836  
   837  	records := make(map[string]backend.Record, len(reqs)) // [token]Record
   838  	for _, v := range reqs {
   839  		// Lookup the record
   840  		r, err := t.tstore.RecordPartial(v.Token, v.Version,
   841  			v.Filenames, v.OmitAllFiles)
   842  		if err != nil {
   843  			if err == backend.ErrRecordNotFound {
   844  				// Record doesn't exist. This is ok. It will not be included
   845  				// in the reply.
   846  				log.Debugf("Record not found %x", v.Token)
   847  				continue
   848  			}
   849  			// An unexpected error occurred. Log it and continue.
   850  			log.Errorf("RecordPartial %x: %v", v.Token, err)
   851  			continue
   852  		}
   853  
   854  		// Update reply. Use whatever token was provided as the key so
   855  		// that the client can validate the reply using the same token
   856  		// that they provided, regardless of whether its a short token
   857  		// or full length token.
   858  		records[util.TokenEncode(v.Token)] = *r
   859  	}
   860  
   861  	return records, nil
   862  }
   863  
   864  // Inventory returns the tokens of records in the inventory categorized by
   865  // record state and record status. The tokens are ordered by the timestamp of
   866  // their most recent status change, sorted from newest to oldest.
   867  //
   868  // The state, status, and page arguments can be provided to request a specific
   869  // page of record tokens.
   870  //
   871  // If no status is provided then the most recent page of tokens for all
   872  // statuses will be returned. All other arguments are ignored.
   873  //
   874  // This function satisfies the backendv2 Backend interface.
   875  func (t *tstoreBackend) Inventory(state backend.StateT, status backend.StatusT, pageSize, pageNumber uint32) (*backend.Inventory, error) {
   876  	log.Tracef("Inventory: %v %v %v %v", state, status, pageSize, pageNumber)
   877  
   878  	inv, err := t.invByStatus(state, status, pageSize, pageNumber)
   879  	if err != nil {
   880  		return nil, err
   881  	}
   882  
   883  	return &backend.Inventory{
   884  		Unvetted: inv.Unvetted,
   885  		Vetted:   inv.Vetted,
   886  	}, nil
   887  }
   888  
   889  // InventoryOrdered returns a page of record tokens ordered by the timestamp of
   890  // their most recent status change. The returned tokens will include all record
   891  // statuses.
   892  //
   893  // This function satisfies the backendv2 Backend interface.
   894  func (t *tstoreBackend) InventoryOrdered(state backend.StateT, pageSize, pageNumber uint32) ([]string, error) {
   895  	log.Tracef("InventoryOrdered: %v %v %v", state, pageSize, pageNumber)
   896  
   897  	tokens, err := t.invOrdered(state, pageSize, pageNumber)
   898  	if err != nil {
   899  		return nil, err
   900  	}
   901  
   902  	return tokens, nil
   903  }
   904  
   905  // PluginRegister registers a plugin.
   906  //
   907  // This function satisfies the backendv2 Backend interface.
   908  func (t *tstoreBackend) PluginRegister(p backend.Plugin) error {
   909  	return t.tstore.PluginRegister(t, p)
   910  }
   911  
   912  // PluginSetup performs any required plugin setup.
   913  //
   914  // This function satisfies the backendv2 Backend interface.
   915  func (t *tstoreBackend) PluginSetup(pluginID string) error {
   916  	log.Tracef("PluginSetup: %v", pluginID)
   917  
   918  	return t.tstore.PluginSetup(pluginID)
   919  }
   920  
   921  // PluginRead executes a read-only plugin command.
   922  //
   923  // This function satisfies the backendv2 Backend interface.
   924  func (t *tstoreBackend) PluginRead(token []byte, pluginID, pluginCmd, payload string) (string, error) {
   925  	log.Tracef("PluginRead: %x %v %v", token, pluginID, pluginCmd)
   926  
   927  	// Verify record exists if a token was provided. The token is
   928  	// optional on read commands so one may not exist.
   929  	if len(token) > 0 && !t.RecordExists(token) {
   930  		return "", backend.ErrRecordNotFound
   931  	}
   932  
   933  	// Execute plugin command
   934  	return t.tstore.PluginRead(token, pluginID, pluginCmd, payload)
   935  }
   936  
   937  // PluginWrite executes a plugin command that writes data.
   938  //
   939  // This function satisfies the backendv2 Backend interface.
   940  func (t *tstoreBackend) PluginWrite(token []byte, pluginID, pluginCmd, payload string) (string, error) {
   941  	log.Tracef("PluginWrite: %x %v %v", token, pluginID, pluginCmd)
   942  
   943  	// Verify record exists
   944  	if !t.RecordExists(token) {
   945  		return "", backend.ErrRecordNotFound
   946  	}
   947  
   948  	log.Infof("Plugin '%v' write cmd '%v' on %x",
   949  		pluginID, pluginCmd, token)
   950  
   951  	// Hold the record lock for the remainder of this function. We
   952  	// do this here in the backend so that the individual plugins
   953  	// implementations don't need to worry about race conditions.
   954  	if t.isShutdown() {
   955  		return "", backend.ErrShutdown
   956  	}
   957  	m := t.recordMutex(token)
   958  	m.Lock()
   959  	defer m.Unlock()
   960  
   961  	// Call pre plugin hooks
   962  	hp := plugins.HookPluginPre{
   963  		Token:    token,
   964  		PluginID: pluginID,
   965  		Cmd:      pluginCmd,
   966  		Payload:  payload,
   967  	}
   968  	b, err := json.Marshal(hp)
   969  	if err != nil {
   970  		return "", err
   971  	}
   972  	err = t.tstore.PluginHookPre(plugins.HookTypePluginPre, string(b))
   973  	if err != nil {
   974  		return "", err
   975  	}
   976  
   977  	// Execute plugin command
   978  	reply, err := t.tstore.PluginWrite(token, pluginID, pluginCmd, payload)
   979  	if err != nil {
   980  		return "", err
   981  	}
   982  
   983  	// Call post plugin hooks
   984  	hpp := plugins.HookPluginPost{
   985  		PluginID: pluginID,
   986  		Cmd:      pluginCmd,
   987  		Payload:  payload,
   988  		Reply:    reply,
   989  	}
   990  	b, err = json.Marshal(hpp)
   991  	if err != nil {
   992  		return "", err
   993  	}
   994  	t.tstore.PluginHookPost(plugins.HookTypePluginPost, string(b))
   995  
   996  	return reply, nil
   997  }
   998  
   999  // PluginInventory returns all registered plugins.
  1000  //
  1001  // This function satisfies the backendv2 Backend interface.
  1002  func (t *tstoreBackend) PluginInventory() []backend.Plugin {
  1003  	log.Tracef("Plugins")
  1004  
  1005  	return t.tstore.Plugins()
  1006  }
  1007  
  1008  // Fsck performs a synchronous filesystem check that verifies the coherency
  1009  // of record and plugin data and caches.
  1010  //
  1011  // This function satisfies the backendv2 Backend interface.
  1012  func (t *tstoreBackend) Fsck() error {
  1013  	log.Infof("Performing fsck on the tstore backend")
  1014  
  1015  	// The tstore backend fsck includes:
  1016  	//
  1017  	// - Rebuilding the inventory cache. The inventory cache contains
  1018  	//   the tokens of all records in backend, categorized by their
  1019  	//   record status and sorted from oldest to newest.
  1020  
  1021  	// Get the tokens for all records in the backend
  1022  	allTokens, err := t.tstore.Inventory()
  1023  	if err != nil {
  1024  		return err
  1025  	}
  1026  
  1027  	log.Infof("%v records found in the tstore backend", len(allTokens))
  1028  	log.Infof("Sorting the records by timestamp")
  1029  
  1030  	// Get the partial record for all tokens
  1031  	records := make(map[string]*backend.Record, len(allTokens))
  1032  	for _, token := range allTokens {
  1033  		r, err := t.tstore.RecordPartial(token, 0, nil, true)
  1034  		if err != nil {
  1035  			return err
  1036  		}
  1037  		records[r.RecordMetadata.Token] = r
  1038  	}
  1039  
  1040  	// Sort the records by their record state: vetted or unvetted.
  1041  	var (
  1042  		vetted   = make([]*backend.Record, 0, len(allTokens))
  1043  		unvetted = make([]*backend.Record, 0, len(allTokens))
  1044  	)
  1045  	for _, token := range allTokens {
  1046  		record := records[hex.EncodeToString(token)]
  1047  		if record.RecordMetadata.State == backend.StateVetted {
  1048  			vetted = append(vetted, record)
  1049  		}
  1050  		if record.RecordMetadata.State == backend.StateUnvetted {
  1051  			unvetted = append(unvetted, record)
  1052  		}
  1053  	}
  1054  
  1055  	// Sort records by the timestamp of their record metadata, from
  1056  	// oldest to newest.
  1057  	//
  1058  	// The order of the record inventory when rebuilt by this function
  1059  	// will be slightly different than the order that is created at
  1060  	// runtime. At runtime, the inventory cache updated when the status
  1061  	// of the record is changed. This fsck function uses the record
  1062  	// metadata timestamp, which is updated anytime the record status
  1063  	// is changed, but also when the record is edited. This means that
  1064  	// the runtime cache that was built will differ from the fsck cache
  1065  	// that is built if there are records that were edited after their
  1066  	// most recent status change. This difference is inconsequential,
  1067  	// so just making a note of it is fine for now.
  1068  	sort.Slice(vetted, func(i, j int) bool {
  1069  		return vetted[i].RecordMetadata.Timestamp <
  1070  			vetted[j].RecordMetadata.Timestamp
  1071  	})
  1072  	sort.Slice(unvetted, func(i, j int) bool {
  1073  		return unvetted[i].RecordMetadata.Timestamp <
  1074  			unvetted[j].RecordMetadata.Timestamp
  1075  	})
  1076  
  1077  	// Delete the inventory cache. We must actually delete
  1078  	// the cache and rebuilt it from scratch instead of just
  1079  	// checking the coherency of it because of the ordering
  1080  	// difference that is noted in the comment above.
  1081  	err = t.invRemoveVetted()
  1082  	if err != nil {
  1083  		return err
  1084  	}
  1085  	err = t.invRemoveUnvetted()
  1086  	if err != nil {
  1087  		return err
  1088  	}
  1089  
  1090  	// Build the vetted inventory cache.
  1091  	//
  1092  	// This is done by adding the record to the unvetted inventory
  1093  	// then moving it to the vetted inventory. This is how it is
  1094  	// done during normal operations, so we do it the same way here
  1095  	// because that is what the inventory API allows for.
  1096  	log.Infof("Building the inventory cache for %v vetted records", len(vetted))
  1097  
  1098  	for _, record := range vetted {
  1099  		bToken, err := hex.DecodeString(record.RecordMetadata.Token)
  1100  		if err != nil {
  1101  			return err
  1102  		}
  1103  		t.inventoryAdd(backend.StateUnvetted, bToken, backend.StatusUnreviewed)
  1104  		t.inventoryMoveToVetted(bToken, record.RecordMetadata.Status)
  1105  	}
  1106  
  1107  	// Build the unvetted inventory cache
  1108  	log.Infof("Building the inventory cache for %v unvetted records",
  1109  		len(unvetted))
  1110  
  1111  	for _, record := range unvetted {
  1112  		bToken, err := hex.DecodeString(record.RecordMetadata.Token)
  1113  		if err != nil {
  1114  			return err
  1115  		}
  1116  		t.inventoryAdd(record.RecordMetadata.State, bToken,
  1117  			record.RecordMetadata.Status)
  1118  	}
  1119  
  1120  	// Update all plugin caches
  1121  	return t.tstore.Fsck(allTokens)
  1122  }
  1123  
  1124  // Close performs cleanup of the backend.
  1125  //
  1126  // This function satisfies the backendv2 Backend interface.
  1127  func (t *tstoreBackend) Close() {
  1128  	log.Tracef("Close")
  1129  
  1130  	t.Lock()
  1131  	defer t.Unlock()
  1132  
  1133  	// Shutdown backend
  1134  	t.shutdown = true
  1135  
  1136  	// Close tstore connections
  1137  	t.tstore.Close()
  1138  }
  1139  
  1140  // setup performs any required work to setup the tstore instance.
  1141  func (t *tstoreBackend) setup() error {
  1142  	return t.tstore.Setup()
  1143  }
  1144  
  1145  // New returns a new tstoreBackend.
  1146  func New(appDir, dataDir string, anp *chaincfg.Params, tlogHost, dbHost, dbPass, dcrtimeHost, dcrtimeCert string) (*tstoreBackend, error) {
  1147  	// Setup tstore instances
  1148  	ts, err := tstore.New(appDir, dataDir, anp, tlogHost,
  1149  		dbHost, dbPass, dcrtimeHost, dcrtimeCert)
  1150  	if err != nil {
  1151  		return nil, fmt.Errorf("new tstore: %v", err)
  1152  	}
  1153  
  1154  	// Setup backend
  1155  	t := tstoreBackend{
  1156  		appDir:     appDir,
  1157  		dataDir:    dataDir,
  1158  		tstore:     ts,
  1159  		recordMtxs: make(map[string]*sync.Mutex),
  1160  	}
  1161  
  1162  	// Perform any required setup
  1163  	err = t.setup()
  1164  	if err != nil {
  1165  		return nil, fmt.Errorf("setup: %v", err)
  1166  	}
  1167  
  1168  	return &t, nil
  1169  }