github.com/decred/politeia@v1.4.0/politeiad/cmd/legacypoliteia/convert.go (about)

     1  // Copyright (c) 2022 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 main
     6  
     7  import (
     8  	"encoding/base64"
     9  	"encoding/hex"
    10  	"encoding/json"
    11  	"fmt"
    12  	"net/http"
    13  	"strconv"
    14  
    15  	backend "github.com/decred/politeia/politeiad/backendv2"
    16  	"github.com/decred/politeia/politeiad/cmd/legacypoliteia/gitbe"
    17  	"github.com/decred/politeia/politeiad/plugins/comments"
    18  	"github.com/decred/politeia/politeiad/plugins/pi"
    19  	"github.com/decred/politeia/politeiad/plugins/ticketvote"
    20  	"github.com/decred/politeia/politeiad/plugins/usermd"
    21  	"github.com/decred/politeia/util"
    22  )
    23  
    24  // convert.go contains the conversion functions for converting git backend
    25  // types into tstore backend and plugin types.
    26  
    27  func convertRecordMetadata(r gitbe.RecordMetadata, version uint32) backend.RecordMetadata {
    28  	return backend.RecordMetadata{
    29  		Token:     r.Token,
    30  		Version:   version, // Parsed from git path
    31  		Iteration: uint32(r.Iteration),
    32  		State:     backend.StateVetted,
    33  		Status:    convertMDStatus(r.Status),
    34  		Timestamp: r.Timestamp,
    35  		Merkle:    r.Merkle,
    36  	}
    37  }
    38  
    39  func convertMDStatus(s gitbe.MDStatusT) backend.StatusT {
    40  	switch s {
    41  	case gitbe.MDStatusInvalid:
    42  		return backend.StatusInvalid
    43  	case gitbe.MDStatusUnvetted:
    44  		return backend.StatusUnreviewed
    45  	case gitbe.MDStatusVetted:
    46  		return backend.StatusPublic
    47  	case gitbe.MDStatusCensored:
    48  		return backend.StatusCensored
    49  	case gitbe.MDStatusIterationUnvetted:
    50  		return backend.StatusUnreviewed
    51  	case gitbe.MDStatusArchived:
    52  		return backend.StatusArchived
    53  	default:
    54  		panic(fmt.Sprintf("invalid md status %v", s))
    55  	}
    56  }
    57  
    58  func convertFile(payload []byte, fileName string) backend.File {
    59  	return backend.File{
    60  		Name:    fileName, // Parsed from git path
    61  		MIME:    http.DetectContentType(payload),
    62  		Digest:  hex.EncodeToString(util.Digest(payload)),
    63  		Payload: base64.StdEncoding.EncodeToString(payload),
    64  	}
    65  }
    66  
    67  func convertProposalMetadata(name string) pi.ProposalMetadata {
    68  	return pi.ProposalMetadata{
    69  		Name:        name, // Parsed from index file
    70  		Amount:      0,
    71  		StartDate:   0,
    72  		EndDate:     0,
    73  		Domain:      "",
    74  		LegacyToken: "", // Populated by the import command
    75  	}
    76  }
    77  
    78  func convertVoteMetadata(pm gitbe.ProposalMetadata) ticketvote.VoteMetadata {
    79  	return ticketvote.VoteMetadata{
    80  		LinkBy: pm.LinkBy,
    81  		LinkTo: pm.LinkTo,
    82  	}
    83  }
    84  
    85  func convertUserMetadata(pg gitbe.ProposalGeneralV2, userID string) usermd.UserMetadata {
    86  	return usermd.UserMetadata{
    87  		UserID:    userID, // Retrieved from politeia API using public key
    88  		PublicKey: pg.PublicKey,
    89  		Signature: pg.Signature,
    90  	}
    91  }
    92  
    93  func convertStatusChange(sc gitbe.RecordStatusChangeV2, token string, version uint32) usermd.StatusChangeMetadata {
    94  	return usermd.StatusChangeMetadata{
    95  		Token:     token,   // Parsed from git path
    96  		Version:   version, // Parsed from git path
    97  		Status:    uint32(convertRecordStatus(sc.NewStatus)),
    98  		Reason:    sc.StatusChangeMessage,
    99  		PublicKey: sc.AdminPubKey,
   100  		// Some signatures may be empty since the signature
   101  		// field is only present on the v2 gitbe status
   102  		// change struct.
   103  		Signature: sc.Signature,
   104  		Timestamp: sc.Timestamp,
   105  	}
   106  }
   107  
   108  func convertRecordStatus(r gitbe.RecordStatusT) backend.StatusT {
   109  	switch r {
   110  	case gitbe.RecordStatusNotReviewed:
   111  		return backend.StatusUnreviewed
   112  	case gitbe.RecordStatusCensored:
   113  		return backend.StatusCensored
   114  	case gitbe.RecordStatusPublic:
   115  		return backend.StatusPublic
   116  	case gitbe.RecordStatusUnreviewedChanges:
   117  		return backend.StatusUnreviewed
   118  	case gitbe.RecordStatusArchived:
   119  		return backend.StatusArchived
   120  	}
   121  	panic(fmt.Sprintf("invalid status %v", r))
   122  }
   123  
   124  func convertCommentAdd(c gitbe.Comment, userID string) comments.CommentAdd {
   125  	parentID, err := strconv.ParseUint(c.ParentID, 10, 64)
   126  	if err != nil {
   127  		panic(err)
   128  	}
   129  	commentID, err := strconv.ParseUint(c.CommentID, 10, 64)
   130  	if err != nil {
   131  		panic(err)
   132  	}
   133  	return comments.CommentAdd{
   134  		UserID:        userID, // Retrieved from the politeia API by public key
   135  		State:         comments.RecordStateVetted,
   136  		Token:         c.Token,
   137  		ParentID:      uint32(parentID),
   138  		Comment:       c.Comment,
   139  		PublicKey:     c.PublicKey,
   140  		Signature:     c.Signature,
   141  		CommentID:     uint32(commentID),
   142  		Version:       1, // Edits were not allowed on legacy comments
   143  		Timestamp:     c.Timestamp,
   144  		Receipt:       c.Receipt,
   145  		ExtraData:     "", // Intentionally omitted
   146  		ExtraDataHint: "", // Intentionally omitted
   147  	}
   148  }
   149  
   150  func convertCommentDel(cc gitbe.CensorComment, parentID uint32, userID string) comments.CommentDel {
   151  	commentID, err := strconv.ParseUint(cc.CommentID, 10, 64)
   152  	if err != nil {
   153  		panic(err)
   154  	}
   155  	return comments.CommentDel{
   156  		Token:     cc.Token,
   157  		State:     comments.RecordStateVetted,
   158  		CommentID: uint32(commentID),
   159  		Reason:    cc.Reason,
   160  		PublicKey: cc.PublicKey,
   161  		Signature: cc.Signature,
   162  		ParentID:  parentID, // Taken from the parent comment
   163  		UserID:    userID,   // Retrieved from the politeia API by public key
   164  		Timestamp: cc.Timestamp,
   165  		Receipt:   cc.Receipt,
   166  	}
   167  }
   168  
   169  func convertCommentVote(lc gitbe.LikeComment, userID string) comments.CommentVote {
   170  	commentID, err := strconv.ParseUint(lc.CommentID, 10, 64)
   171  	if err != nil {
   172  		panic(err)
   173  	}
   174  	var vote comments.VoteT
   175  	switch {
   176  	case lc.Action == "1":
   177  		vote = comments.VoteUpvote
   178  	case lc.Action == "-1":
   179  		vote = comments.VoteDownvote
   180  	default:
   181  		panic("invalid comment vote code")
   182  	}
   183  	return comments.CommentVote{
   184  		UserID:    userID, // Retrieved from the politeia API by public key
   185  		State:     comments.RecordStateVetted,
   186  		Token:     lc.Token,
   187  		CommentID: uint32(commentID),
   188  		Vote:      vote,
   189  		PublicKey: lc.PublicKey,
   190  		Signature: lc.Signature,
   191  		Timestamp: lc.Timestamp,
   192  		Receipt:   lc.Receipt,
   193  	}
   194  }
   195  
   196  func convertVoteDetails(startVoteJSON []byte, svr gitbe.StartVoteReply, version uint32, voteMD *ticketvote.VoteMetadata) ticketvote.VoteDetails {
   197  	// The start vote structure has a v1 and v2.
   198  	// The fields that we need are pulled out of
   199  	// the specific structure.
   200  	var (
   201  		token           string
   202  		proposalVersion uint32
   203  		voteType        ticketvote.VoteT
   204  		mask            uint64
   205  		duration        uint32
   206  		quorum          uint32
   207  		pass            uint32
   208  		options         []ticketvote.VoteOption
   209  		publicKey       string
   210  	)
   211  	structVersion, err := decodeVersion(startVoteJSON)
   212  	if err != nil {
   213  		panic(err)
   214  	}
   215  	switch structVersion {
   216  	case 1:
   217  		// Decode the start vote
   218  		var sv gitbe.StartVoteV1
   219  		err = json.Unmarshal(startVoteJSON, &sv)
   220  		if err != nil {
   221  			panic(err)
   222  		}
   223  
   224  		// Pull the fields that we need
   225  		token = sv.Vote.Token
   226  		proposalVersion = version
   227  		voteType = ticketvote.VoteTypeStandard
   228  		mask = sv.Vote.Mask
   229  		duration = sv.Vote.Duration
   230  		quorum = sv.Vote.QuorumPercentage
   231  		pass = sv.Vote.PassPercentage
   232  		options = convertVoteOptions(sv.Vote.Options)
   233  		publicKey = sv.PublicKey
   234  
   235  	case 2:
   236  		// Decode the start vote
   237  		var sv gitbe.StartVoteV2
   238  		err = json.Unmarshal(startVoteJSON, &sv)
   239  		if err != nil {
   240  			panic(err)
   241  		}
   242  
   243  		// Sanity check proposal version. The version in the start vote
   244  		// should be the same version from the proposal directory path.
   245  		if version != sv.Vote.ProposalVersion {
   246  			panic(fmt.Sprintf("start vote version mismatch: %v %v",
   247  				version, sv.Vote.ProposalVersion))
   248  		}
   249  
   250  		// Pull the fields that we need
   251  		token = sv.Vote.Token
   252  		proposalVersion = version
   253  		voteType = convertVoteType(sv.Vote.Type)
   254  		mask = sv.Vote.Mask
   255  		duration = sv.Vote.Duration
   256  		quorum = sv.Vote.QuorumPercentage
   257  		pass = sv.Vote.PassPercentage
   258  		options = convertVoteOptions(sv.Vote.Options)
   259  		publicKey = sv.PublicKey
   260  
   261  	default:
   262  		panic(fmt.Sprintf("invalid start vote version '%v'", structVersion))
   263  	}
   264  
   265  	// Populate parent if it's a RFP submission
   266  	var parent string
   267  	if voteMD != nil {
   268  		parent = voteMD.LinkTo
   269  	}
   270  
   271  	startHeight, err := strconv.ParseUint(svr.StartBlockHeight, 10, 32)
   272  	if err != nil {
   273  		panic(err)
   274  	}
   275  	endHeight, err := strconv.ParseUint(svr.EndHeight, 10, 32)
   276  	if err != nil {
   277  		panic(err)
   278  	}
   279  
   280  	// The Signature and Receipt have been omitted because the
   281  	// message being signed changes between the git backend and
   282  	// the tstore backend. Transferring the old signature would
   283  	// result in invalid signature errors.
   284  	return ticketvote.VoteDetails{
   285  		Params: ticketvote.VoteParams{
   286  			Token:            token,
   287  			Version:          proposalVersion, // Parsed from git path
   288  			Type:             voteType,
   289  			Mask:             mask,
   290  			Duration:         duration,
   291  			QuorumPercentage: quorum,
   292  			PassPercentage:   pass,
   293  			Options:          options,
   294  			Parent:           parent,
   295  		},
   296  		PublicKey:        publicKey,
   297  		Signature:        "", // Intentionally omitted
   298  		Receipt:          "", // Intentionally omitted
   299  		StartBlockHeight: uint32(startHeight),
   300  		StartBlockHash:   svr.StartBlockHash,
   301  		EndBlockHeight:   uint32(endHeight),
   302  		EligibleTickets:  svr.EligibleTickets,
   303  	}
   304  }
   305  
   306  func convertVoteOptions(options []gitbe.VoteOption) []ticketvote.VoteOption {
   307  	opts := make([]ticketvote.VoteOption, 0, len(options))
   308  	for _, v := range options {
   309  		opts = append(opts, ticketvote.VoteOption{
   310  			ID:          v.Id,
   311  			Description: v.Description,
   312  			Bit:         v.Bits,
   313  		})
   314  	}
   315  	return opts
   316  }
   317  
   318  func convertVoteType(t gitbe.VoteT) ticketvote.VoteT {
   319  	switch t {
   320  	case gitbe.VoteTypeStandard:
   321  		return ticketvote.VoteTypeStandard
   322  	case gitbe.VoteTypeRunoff:
   323  		return ticketvote.VoteTypeRunoff
   324  	}
   325  	panic(fmt.Sprintf("invalid vote type %v", t))
   326  }
   327  
   328  func convertCastVoteDetails(cvj gitbe.CastVoteJournal, address string, timestamp int64) ticketvote.CastVoteDetails {
   329  	return ticketvote.CastVoteDetails{
   330  		Token:     cvj.CastVote.Token,
   331  		Ticket:    cvj.CastVote.Ticket,
   332  		VoteBit:   cvj.CastVote.VoteBit,
   333  		Signature: cvj.CastVote.Signature,
   334  		Address:   address, // Retrieved from dcrdata
   335  		Receipt:   cvj.Receipt,
   336  		// Timestamp will be the timestamp of the git commit that
   337  		// added the vote to the ballots journal. The exact
   338  		// timestamp of when the vote was cast does not exist.
   339  		Timestamp: timestamp,
   340  	}
   341  }
   342  
   343  // decodeVersion returns the version field from the provided JSON payload. This
   344  // function should only be used when the payload contains a single struct with
   345  // a "version" field.
   346  func decodeVersion(payload []byte) (uint, error) {
   347  	data := make(map[string]interface{}, 32)
   348  	err := json.Unmarshal(payload, &data)
   349  	if err != nil {
   350  		return 0, err
   351  	}
   352  	version := uint(data["version"].(float64))
   353  	if version == 0 {
   354  		return 0, fmt.Errorf("version not found")
   355  	}
   356  	return version, nil
   357  }