github.com/decred/politeia@v1.4.0/politeiad/backendv2/backendv2.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 backendv2
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  
    11  	"github.com/decred/politeia/politeiad/api/v1/identity"
    12  )
    13  
    14  var (
    15  	// ErrShutdown is returned when the backend is shutdown.
    16  	ErrShutdown = errors.New("backend is shutdown")
    17  
    18  	// ErrTokenInvalid is returned when a token is invalid.
    19  	ErrTokenInvalid = errors.New("token is invalid")
    20  
    21  	// ErrRecordNotFound is returned when a record is not found.
    22  	ErrRecordNotFound = errors.New("record not found")
    23  
    24  	// ErrRecordLocked is returned when a record is attempted to be
    25  	// updated but the record status does not allow further updates.
    26  	ErrRecordLocked = errors.New("record is locked")
    27  
    28  	// ErrNoRecordChanges is returned when a record update does not
    29  	// contain any changes.
    30  	ErrNoRecordChanges = errors.New("no record changes")
    31  
    32  	// ErrPluginIDInvalid is returned when a invalid plugin ID is used.
    33  	ErrPluginIDInvalid = errors.New("plugin id invalid")
    34  
    35  	// ErrPluginCmdInvalid is returned when a invalid plugin command is
    36  	// used.
    37  	ErrPluginCmdInvalid = errors.New("plugin command invalid")
    38  
    39  	// ErrDuplicatePayload is returned when a duplicate payload is sent to
    40  	// a plugin, where it tries to write data that already exists. Timestamp
    41  	// data relies on the hash of the payload, therefore duplicate payloads
    42  	// are not allowed since they will cause collisions.
    43  	ErrDuplicatePayload = errors.New("duplicate payload")
    44  )
    45  
    46  // StateT represents the state of a record.
    47  type StateT uint32
    48  
    49  const (
    50  	// StateInvalid is an invalid record state.
    51  	StateInvalid StateT = 0
    52  
    53  	// StateUnvetted indicates a record has not been made public.
    54  	StateUnvetted StateT = 1
    55  
    56  	// StateVetted indicates a record has been made public.
    57  	StateVetted StateT = 2
    58  
    59  	// StateLast used for unit test only.
    60  	StateLast StateT = 3
    61  )
    62  
    63  var (
    64  	// States contains the human readable record states.
    65  	States = map[StateT]string{
    66  		StateInvalid:  "invalid",
    67  		StateUnvetted: "unvetted",
    68  		StateVetted:   "vetted",
    69  	}
    70  )
    71  
    72  // StatusT represents the status of a record.
    73  type StatusT uint32
    74  
    75  const (
    76  	// StatusInvalid is an invalid status code.
    77  	StatusInvalid StatusT = 0
    78  
    79  	// StatusUnreviewed indicates a record has not been made public
    80  	// yet. The state of an unreviewed record will always be unvetted.
    81  	StatusUnreviewed StatusT = 1
    82  
    83  	// StatusPublic indicates a record has been made public. The state
    84  	// of a public record will always be vetted.
    85  	StatusPublic StatusT = 2
    86  
    87  	// StatusCensored indicates a record has been censored. A censored
    88  	// record is locked from any further updates and all record content
    89  	// is permanently deleted. A censored record can have a state of
    90  	// either unvetted or vetted.
    91  	StatusCensored StatusT = 3
    92  
    93  	// StatusArchived indicates a record has been archived. An archived
    94  	// record is locked from any further updates. An archived record
    95  	// have a state of either unvetted or vetted.
    96  	StatusArchived StatusT = 4
    97  
    98  	// StatusLast is used for unit test validation of human readable
    99  	// errors.
   100  	StatusLast StatusT = 5
   101  )
   102  
   103  var (
   104  	// Statuses contains the human readable record statuses.
   105  	Statuses = map[StatusT]string{
   106  		StatusInvalid:    "invalid",
   107  		StatusUnreviewed: "unreviewed",
   108  		StatusPublic:     "public",
   109  		StatusCensored:   "censored",
   110  		StatusArchived:   "archived",
   111  	}
   112  )
   113  
   114  // StatusTransitionError indicates an invalid record status transition.
   115  type StatusTransitionError struct {
   116  	From StatusT
   117  	To   StatusT
   118  }
   119  
   120  // Error satisfies the error interface.
   121  func (s StatusTransitionError) Error() string {
   122  	return fmt.Sprintf("invalid record status transition %v (%v) -> %v (%v)",
   123  		Statuses[s.From], s.From, Statuses[s.To], s.To)
   124  }
   125  
   126  // RecordMetadata represents metadata that is created by the backend on record
   127  // submission and updates.
   128  //
   129  // The record version is incremented anytime the record files are updated. The
   130  // record iteration is incremented anytime record files, metadata, or the
   131  // record status are updated.
   132  type RecordMetadata struct {
   133  	Token     string  `json:"token"`     // Record identifier, hex encoded
   134  	Version   uint32  `json:"version"`   // Record version
   135  	Iteration uint32  `json:"iteration"` // Record iteration
   136  	State     StateT  `json:"state"`     // Unvetted or vetted
   137  	Status    StatusT `json:"status"`    // Record status
   138  	Timestamp int64   `json:"timestamp"` // Last updated
   139  	Merkle    string  `json:"merkle"`    // Merkle root of record files
   140  }
   141  
   142  // MetadataStream describes a single metada stream.
   143  type MetadataStream struct {
   144  	PluginID string `json:"pluginid"` // Plugin identity
   145  	StreamID uint32 `json:"streamid"` // Stream identity
   146  	Payload  string `json:"payload"`  // JSON encoded metadata
   147  }
   148  
   149  // File represents a record file.
   150  type File struct {
   151  	Name    string `json:"name"`    // Basename of the file
   152  	MIME    string `json:"mime"`    // MIME type
   153  	Digest  string `json:"digest"`  // SHA256 of decoded Payload
   154  	Payload string `json:"payload"` // Base64 encoded file payload
   155  }
   156  
   157  // Record is a permanent record that includes the submitted files, metadata and
   158  // internal metadata.
   159  type Record struct {
   160  	RecordMetadata RecordMetadata   `json:"recordmetadata"`
   161  	Metadata       []MetadataStream `json:"metadata"`
   162  	Files          []File           `json:"files"`
   163  }
   164  
   165  // ContentErrorCodeT represents a record content error.
   166  type ContentErrorCodeT uint32
   167  
   168  const (
   169  	ContentErrorInvalid                 ContentErrorCodeT = 0
   170  	ContentErrorMetadataStreamInvalid   ContentErrorCodeT = 1
   171  	ContentErrorMetadataStreamDuplicate ContentErrorCodeT = 2
   172  	ContentErrorFilesEmpty              ContentErrorCodeT = 3
   173  	ContentErrorFileNameInvalid         ContentErrorCodeT = 4
   174  	ContentErrorFileNameDuplicate       ContentErrorCodeT = 5
   175  	ContentErrorFileDigestInvalid       ContentErrorCodeT = 6
   176  	ContentErrorFilePayloadInvalid      ContentErrorCodeT = 7
   177  	ContentErrorFileMIMETypeInvalid     ContentErrorCodeT = 8
   178  	ContentErrorFileMIMETypeUnsupported ContentErrorCodeT = 9
   179  )
   180  
   181  // ContentError is returned when the content of a record does not pass
   182  // validation.
   183  type ContentError struct {
   184  	ErrorCode    ContentErrorCodeT `json:"errorcode"`
   185  	ErrorContext string            `json:"errorcontext"`
   186  }
   187  
   188  // Error satisfies the error interface.
   189  func (e ContentError) Error() string {
   190  	return fmt.Sprintf("content error code: %v", e.ErrorCode)
   191  }
   192  
   193  // RecordRequest is used to request a record. It gives the caller granular
   194  // control over what is returned. The only required field is the token. All
   195  // other fields are optional. All record files are returned by default unless
   196  // one of the file arguments is provided.
   197  //
   198  // Version is used to request a specific version of a record. If no version is
   199  // provided then the most recent version of the record will be returned.
   200  //
   201  // Filenames can be used to request specific files. If filenames is not empty
   202  // then the specified files will be the only files that are returned.
   203  //
   204  // OmitAllFiles can be used to retrieve a record without any of the record
   205  // files. This supersedes the filenames argument.
   206  type RecordRequest struct {
   207  	Token        []byte
   208  	Version      uint32
   209  	Filenames    []string
   210  	OmitAllFiles bool
   211  }
   212  
   213  // Proof contains an inclusion proof for the digest in the merkle root. All
   214  // digests are hex encoded SHA256 digests.
   215  //
   216  // The ExtraData field is used by certain types of proofs to include additional
   217  // data that is required to validate the proof.
   218  type Proof struct {
   219  	Type       string
   220  	Digest     string
   221  	MerkleRoot string
   222  	MerklePath []string
   223  	ExtraData  string // JSON encoded
   224  }
   225  
   226  // Timestamp contains all of the data required to verify that a piece of record
   227  // content was timestamped onto the decred blockchain.
   228  //
   229  // All digests are hex encoded SHA256 digests. The merkle root can be found in
   230  // the OP_RETURN of the specified DCR transaction.
   231  //
   232  // TxID, MerkleRoot, and Proofs will only be populated once the merkle root has
   233  // been included in a DCR tx and the tx has 6 confirmations. The Data field
   234  // will not be populated if the data has been censored.
   235  type Timestamp struct {
   236  	Data       string // JSON encoded
   237  	Digest     string
   238  	TxID       string
   239  	MerkleRoot string
   240  	Proofs     []Proof
   241  }
   242  
   243  // RecordTimestamps contains a Timestamp for all record data.
   244  type RecordTimestamps struct {
   245  	RecordMetadata Timestamp
   246  
   247  	Metadata map[string]map[uint32]Timestamp // [pluginID][streamID]Timestamp
   248  	Files    map[string]Timestamp            // map[filename]Timestamp
   249  }
   250  
   251  // Inventory contains the tokens of records in the inventory categorized by
   252  // record state and record status. Tokens are sorted by the timestamp of the
   253  // status change from newest to oldest.
   254  type Inventory struct {
   255  	Unvetted map[StatusT][]string
   256  	Vetted   map[StatusT][]string
   257  }
   258  
   259  // PluginSetting represents a configurable plugin setting.
   260  //
   261  // The value can either contain a single value or multiple values. Multiple
   262  // values will be formatted as a JSON encoded []string.
   263  type PluginSetting struct {
   264  	Key   string // Name of setting
   265  	Value string // Value of setting
   266  }
   267  
   268  // Plugin describes a plugin and its settings.
   269  type Plugin struct {
   270  	ID       string
   271  	Settings []PluginSetting
   272  
   273  	// Identity contains the full identity that the plugin uses to
   274  	// create receipts, i.e. signatures of user provided data that
   275  	// prove the backend received and processed a plugin command.
   276  	Identity *identity.FullIdentity
   277  }
   278  
   279  // PluginError represents an error that occurred during plugin execution that
   280  // was caused by the user.
   281  type PluginError struct {
   282  	PluginID     string
   283  	ErrorCode    uint32
   284  	ErrorContext string
   285  }
   286  
   287  // Error satisfies the error interface.
   288  func (e PluginError) Error() string {
   289  	return fmt.Sprintf("%v plugin error code %v",
   290  		e.PluginID, e.ErrorCode)
   291  }
   292  
   293  // Backend provides an API for interacting with records in the backend.
   294  type Backend interface {
   295  	// RecordNew creates a new record.
   296  	RecordNew([]MetadataStream, []File) (*Record, error)
   297  
   298  	// RecordEdit edits an existing record.
   299  	RecordEdit(token []byte, mdAppend, mdOverwrite []MetadataStream,
   300  		filesAdd []File, filesDel []string) (*Record, error)
   301  
   302  	// RecordEditMetadata edits the metadata of a record without
   303  	// editing any record files.
   304  	RecordEditMetadata(token []byte, mdAppend,
   305  		mdOverwrite []MetadataStream) (*Record, error)
   306  
   307  	// RecordSetStatus sets the status of a record.
   308  	RecordSetStatus(token []byte, s StatusT, mdAppend,
   309  		mdOverwrite []MetadataStream) (*Record, error)
   310  
   311  	// RecordExists returns whether a record exists.
   312  	RecordExists(token []byte) bool
   313  
   314  	// RecordTimestamps returns the timestamps for a record. If no
   315  	// version is provided then timestamps for the most recent version
   316  	// will be returned.
   317  	RecordTimestamps(token []byte, version uint32) (*RecordTimestamps, error)
   318  
   319  	// Records retreives a batch of records. If a record is not found
   320  	// then it is simply not included in the returned map. An error is
   321  	// not returned.
   322  	Records(reqs []RecordRequest) (map[string]Record, error)
   323  
   324  	// Inventory returns the tokens of records in the inventory
   325  	// categorized by record state and record status. The tokens are
   326  	// ordered by the timestamp of their most recent status change,
   327  	// sorted from newest to oldest.
   328  	//
   329  	// The state, status, and page arguments can be provided to request
   330  	// a specific page of record tokens.
   331  	//
   332  	// If no status is provided then the most recent page of tokens for
   333  	// all statuses will be returned. All other arguments are ignored.
   334  	Inventory(state StateT, status StatusT, pageSize,
   335  		pageNumber uint32) (*Inventory, error)
   336  
   337  	// InventoryOrdered returns a page of record tokens ordered by the
   338  	// timestamp of their most recent status change from newest to
   339  	// oldest. The returned tokens will include all record statuses.
   340  	InventoryOrdered(s StateT, pageSize, pageNumber uint32) ([]string, error)
   341  
   342  	// PluginRegister registers a plugin.
   343  	PluginRegister(Plugin) error
   344  
   345  	// PluginSetup performs any required plugin setup.
   346  	PluginSetup(pluginID string) error
   347  
   348  	// PluginRead executes a read-only plugin command.
   349  	PluginRead(token []byte, pluginID, pluginCmd,
   350  		payload string) (string, error)
   351  
   352  	// PluginWrite executes a plugin command that writes data.
   353  	PluginWrite(token []byte, pluginID, pluginCmd,
   354  		payload string) (string, error)
   355  
   356  	// PluginInventory returns all registered plugins.
   357  	PluginInventory() []Plugin
   358  
   359  	// Fsck performs a synchronous filesystem check that verifies
   360  	// the coherency of record and plugin data and caches.
   361  	Fsck() error
   362  
   363  	// Close performs cleanup of the backend.
   364  	Close()
   365  }