github.com/NebulousLabs/Sia@v1.3.7/modules/renter/contractor/persist_journal.go (about)

     1  package contractor
     2  
     3  // The contractor achieves efficient persistence using a JSON transaction
     4  // journal. It enables efficient ACID transactions on JSON objects.
     5  //
     6  // The journal represents a single JSON object, containing all of the
     7  // contractor's persisted data. The object is serialized as an "initial
     8  // object" followed by a series of update sets, one per line. Each update
     9  // specifies a modification.
    10  //
    11  // During operation, the object is first loaded by reading the file and
    12  // applying each update to the initial object. It is subsequently modified by
    13  // appending update sets to the file, one per line. At any time, a
    14  // "checkpoint" may be created, which clears the journal and starts over with
    15  // a new initial object. This allows for compaction of the journal file.
    16  //
    17  // In the event of power failure or other serious disruption, the most recent
    18  // update set may be only partially written. Partially written update sets are
    19  // simply ignored when reading the journal.
    20  
    21  import (
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"io"
    26  	"os"
    27  
    28  	"github.com/NebulousLabs/Sia/build"
    29  	"github.com/NebulousLabs/Sia/crypto"
    30  	"github.com/NebulousLabs/Sia/modules"
    31  	"github.com/NebulousLabs/Sia/modules/renter/proto"
    32  	"github.com/NebulousLabs/Sia/persist"
    33  	"github.com/NebulousLabs/Sia/types"
    34  )
    35  
    36  var journalMeta = persist.Metadata{
    37  	Header:  "Contractor Journal",
    38  	Version: "1.1.1",
    39  }
    40  
    41  type journalPersist struct {
    42  	Allowance       modules.Allowance                   `json:"allowance"`
    43  	BlockHeight     types.BlockHeight                   `json:"blockheight"`
    44  	CachedRevisions map[string]proto.V130CachedRevision `json:"cachedrevisions"`
    45  	Contracts       map[string]proto.V130Contract       `json:"contracts"`
    46  	CurrentPeriod   types.BlockHeight                   `json:"currentperiod"`
    47  	LastChange      modules.ConsensusChangeID           `json:"lastchange"`
    48  	OldContracts    []proto.V130Contract                `json:"oldcontracts"`
    49  	RenewedIDs      map[string]string                   `json:"renewedids"`
    50  }
    51  
    52  // A journal is a log of updates to a JSON object.
    53  type journal struct {
    54  	f        *os.File
    55  	filename string
    56  }
    57  
    58  // update applies the updateSet atomically to j. It syncs the underlying file
    59  // before returning.
    60  func (j *journal) update(us updateSet) error {
    61  	if err := json.NewEncoder(j.f).Encode(us); err != nil {
    62  		return err
    63  	}
    64  	return j.f.Sync()
    65  }
    66  
    67  // Close closes the underlying file.
    68  func (j *journal) Close() error {
    69  	return j.f.Close()
    70  }
    71  
    72  // openJournal opens the supplied journal and decodes the reconstructed
    73  // journalPersist into data.
    74  func openJournal(filename string, data *journalPersist) (*journal, error) {
    75  	// Open file handle for reading and writing.
    76  	f, err := os.OpenFile(filename, os.O_RDWR, 0)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	// Decode the metadata.
    82  	dec := json.NewDecoder(f)
    83  	var meta persist.Metadata
    84  	if err = dec.Decode(&meta); err != nil {
    85  		return nil, err
    86  	} else if meta.Header != journalMeta.Header {
    87  		return nil, fmt.Errorf("expected header %q, got %q", journalMeta.Header, meta.Header)
    88  	} else if meta.Version != journalMeta.Version {
    89  		return nil, fmt.Errorf("journal version (%s) is incompatible with the current version (%s)", meta.Version, journalMeta.Version)
    90  	}
    91  
    92  	// Decode the initial object.
    93  	if err = dec.Decode(data); err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	// Make sure all maps are properly initialized.
    98  	if data.CachedRevisions == nil {
    99  		data.CachedRevisions = map[string]proto.V130CachedRevision{}
   100  	}
   101  	if data.Contracts == nil {
   102  		data.Contracts = map[string]proto.V130Contract{}
   103  	}
   104  	if data.RenewedIDs == nil {
   105  		data.RenewedIDs = map[string]string{}
   106  	}
   107  
   108  	// Decode each set of updates and apply them to data.
   109  	for {
   110  		var set updateSet
   111  		if err = dec.Decode(&set); err == io.EOF || err == io.ErrUnexpectedEOF {
   112  			// unexpected EOF means the last update was corrupted; skip it
   113  			break
   114  		} else if err != nil {
   115  			// skip corrupted update sets
   116  			continue
   117  		}
   118  		for _, u := range set {
   119  			u.apply(data)
   120  		}
   121  	}
   122  
   123  	return &journal{
   124  		f:        f,
   125  		filename: filename,
   126  	}, nil
   127  }
   128  
   129  type journalUpdate interface {
   130  	apply(*journalPersist)
   131  }
   132  
   133  type marshaledUpdate struct {
   134  	Type     string          `json:"type"`
   135  	Data     json.RawMessage `json:"data"`
   136  	Checksum crypto.Hash     `json:"checksum"`
   137  }
   138  
   139  type updateSet []journalUpdate
   140  
   141  // UnmarshalJSON unmarshals an array of marshaledUpdates as a set of
   142  // journalUpdates.
   143  func (set *updateSet) UnmarshalJSON(b []byte) error {
   144  	var marshaledSet []marshaledUpdate
   145  	if err := json.Unmarshal(b, &marshaledSet); err != nil {
   146  		return err
   147  	}
   148  	for _, u := range marshaledSet {
   149  		if crypto.HashBytes(u.Data) != u.Checksum {
   150  			return errors.New("bad checksum")
   151  		}
   152  		var err error
   153  		switch u.Type {
   154  		case "uploadRevision":
   155  			var ur updateUploadRevision
   156  			err = json.Unmarshal(u.Data, &ur)
   157  			*set = append(*set, ur)
   158  		case "downloadRevision":
   159  			var dr updateDownloadRevision
   160  			err = json.Unmarshal(u.Data, &dr)
   161  			*set = append(*set, dr)
   162  		case "cachedUploadRevision":
   163  			var cur updateCachedUploadRevision
   164  			err = json.Unmarshal(u.Data, &cur)
   165  			*set = append(*set, cur)
   166  		case "cachedDownloadRevision":
   167  			var cdr updateCachedDownloadRevision
   168  			err = json.Unmarshal(u.Data, &cdr)
   169  			*set = append(*set, cdr)
   170  		}
   171  		if err != nil {
   172  			return err
   173  		}
   174  	}
   175  	return nil
   176  }
   177  
   178  // updateUploadRevision is a journalUpdate that records the new data
   179  // associated with uploading a sector to a host.
   180  type updateUploadRevision struct {
   181  	NewRevisionTxn     types.Transaction `json:"newrevisiontxn"`
   182  	NewSectorRoot      crypto.Hash       `json:"newsectorroot"`
   183  	NewSectorIndex     int               `json:"newsectorindex"`
   184  	NewUploadSpending  types.Currency    `json:"newuploadspending"`
   185  	NewStorageSpending types.Currency    `json:"newstoragespending"`
   186  }
   187  
   188  // apply sets the LastRevision, LastRevisionTxn, UploadSpending, and
   189  // DownloadSpending fields of the contract being revised. It also adds the new
   190  // Merkle root to the contract's Merkle root set.
   191  func (u updateUploadRevision) apply(data *journalPersist) {
   192  	if len(u.NewRevisionTxn.FileContractRevisions) == 0 {
   193  		build.Critical("updateUploadRevision is missing its FileContractRevision")
   194  		return
   195  	}
   196  
   197  	rev := u.NewRevisionTxn.FileContractRevisions[0]
   198  	c := data.Contracts[rev.ParentID.String()]
   199  	c.LastRevisionTxn = u.NewRevisionTxn
   200  
   201  	if u.NewSectorIndex == len(c.MerkleRoots) {
   202  		c.MerkleRoots = append(c.MerkleRoots, u.NewSectorRoot)
   203  	} else if u.NewSectorIndex < len(c.MerkleRoots) {
   204  		c.MerkleRoots[u.NewSectorIndex] = u.NewSectorRoot
   205  	} else {
   206  		// Shouldn't happen. TODO: Correctly handle error.
   207  	}
   208  
   209  	c.UploadSpending = u.NewUploadSpending
   210  	c.StorageSpending = u.NewStorageSpending
   211  	data.Contracts[rev.ParentID.String()] = c
   212  }
   213  
   214  // updateUploadRevision is a journalUpdate that records the new data
   215  // associated with downloading a sector from a host.
   216  type updateDownloadRevision struct {
   217  	NewRevisionTxn      types.Transaction `json:"newrevisiontxn"`
   218  	NewDownloadSpending types.Currency    `json:"newdownloadspending"`
   219  }
   220  
   221  // apply sets the LastRevision, LastRevisionTxn, and DownloadSpending fields
   222  // of the contract being revised.
   223  func (u updateDownloadRevision) apply(data *journalPersist) {
   224  	if len(u.NewRevisionTxn.FileContractRevisions) == 0 {
   225  		build.Critical("updateDownloadRevision is missing its FileContractRevision")
   226  		return
   227  	}
   228  	rev := u.NewRevisionTxn.FileContractRevisions[0]
   229  	c := data.Contracts[rev.ParentID.String()]
   230  	c.LastRevisionTxn = u.NewRevisionTxn
   231  	c.DownloadSpending = u.NewDownloadSpending
   232  	data.Contracts[rev.ParentID.String()] = c
   233  }
   234  
   235  // updateCachedUploadRevision is a journalUpdate that records the unsigned
   236  // revision sent to the host during a sector upload, along with the Merkle
   237  // root of the new sector.
   238  type updateCachedUploadRevision struct {
   239  	Revision    types.FileContractRevision `json:"revision"`
   240  	SectorRoot  crypto.Hash                `json:"sectorroot"`
   241  	SectorIndex int                        `json:"sectorindex"`
   242  }
   243  
   244  // apply sets the Revision field of the cachedRevision associated with the
   245  // contract being revised, as well as the Merkle root of the new sector.
   246  func (u updateCachedUploadRevision) apply(data *journalPersist) {
   247  	c := data.CachedRevisions[u.Revision.ParentID.String()]
   248  	c.Revision = u.Revision
   249  	if u.SectorIndex == len(c.MerkleRoots) {
   250  		c.MerkleRoots = append(c.MerkleRoots, u.SectorRoot)
   251  	} else if u.SectorIndex < len(c.MerkleRoots) {
   252  		c.MerkleRoots[u.SectorIndex] = u.SectorRoot
   253  	} else {
   254  		// Shouldn't happen. TODO: Add correct error handling.
   255  	}
   256  	data.CachedRevisions[u.Revision.ParentID.String()] = c
   257  }
   258  
   259  // updateCachedDownloadRevision is a journalUpdate that records the unsigned
   260  // revision sent to the host during a sector download.
   261  type updateCachedDownloadRevision struct {
   262  	Revision types.FileContractRevision `json:"revision"`
   263  }
   264  
   265  // apply sets the Revision field of the cachedRevision associated with the
   266  // contract being revised.
   267  func (u updateCachedDownloadRevision) apply(data *journalPersist) {
   268  	c := data.CachedRevisions[u.Revision.ParentID.String()]
   269  	c.Revision = u.Revision
   270  	data.CachedRevisions[u.Revision.ParentID.String()] = c
   271  }