Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi: Add RFP proposal. #1054

Merged
merged 35 commits into from May 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8386daa
multi: Add RFP proposal and runoff vote.
lukebp Feb 18, 2020
c087c79
.
lukebp Mar 12, 2020
d5c4537
rebase master
lukebp Apr 4, 2020
4be0dcc
cleanup
lukebp Apr 4, 2020
bdc5948
.
lukebp Apr 5, 2020
675f53f
.
lukebp Apr 5, 2020
87bc8d8
fix VoteResults bugs
lukebp Apr 6, 2020
c85bcee
.
lukebp Apr 6, 2020
84debc4
update PolicyReply
lukebp Apr 9, 2020
407d325
typo
lukebp Apr 9, 2020
6eb810a
fix EditProposal bug
lukebp Apr 10, 2020
437c5b5
filename typos
lukebp Apr 10, 2020
c0d20b4
Merge branch 'master' into rfp
lukebp Apr 17, 2020
560643d
enforce max linkby
lukebp Apr 17, 2020
1bc61e2
linkby validation improvements
lukebp Apr 17, 2020
d88e009
votesummary bug fix
lukebp Apr 17, 2020
b9d2aa2
no disk reads from decred plugin
lukebp Apr 17, 2020
4ee2122
cleanup
lukebp Apr 17, 2020
2b7f85d
runoff submission version validation
lukebp Apr 20, 2020
2e4ed77
linkby validation bug fix
lukebp Apr 21, 2020
9c835a8
vote status bug
lukebp Apr 21, 2020
b8cd681
vote summary bug fix part II
lukebp Apr 22, 2020
df36c4a
unaddressable value bug
lukebp Apr 22, 2020
a1a89b5
vote summary fix
lukebp Apr 22, 2020
dd93ad9
Add authorize votes to runoff votes
lukebp Apr 27, 2020
0f492a5
Merge branch 'master' into rfp
lukebp May 4, 2020
87b8b4f
fix issues from merge
lukebp May 4, 2020
6020748
more fixes
lukebp May 4, 2020
e2a4703
squash bugs
lukebp May 4, 2020
e3cc464
Add authorize vote replies
lukebp May 4, 2020
f6799b9
Merge branch 'master' into rfp
lukebp May 5, 2020
443d8c2
update api docs
lukebp May 5, 2020
4b3ddbc
typos
lukebp May 5, 2020
a992530
make minlinkby dynamic
lukebp May 6, 2020
0b2cea5
cli fix
lukebp May 6, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
129 changes: 126 additions & 3 deletions decredplugin/decredplugin.go
Expand Up @@ -18,6 +18,7 @@ const (
ID = "decred"
CmdAuthorizeVote = "authorizevote"
CmdStartVote = "startvote"
CmdStartVoteRunoff = "startvoterunoff"
CmdVoteDetails = "votedetails"
CmdVoteSummary = "votesummary"
CmdBatchVoteSummary = "batchvotesummary"
Expand All @@ -35,6 +36,7 @@ const (
CmdProposalCommentsLikes = "proposalcommentslikes"
CmdInventory = "inventory"
CmdTokenInventory = "tokeninventory"
CmdLinkedFrom = "linkedfrom"
MDStreamAuthorizeVote = 13 // Vote authorization by proposal author
MDStreamVoteBits = 14 // Vote bits and mask
MDStreamVoteSnapshot = 15 // Vote tickets and start/end parameters
Expand All @@ -46,13 +48,29 @@ const (
AuthVoteActionAuthorize = "authorize" // Authorize a proposal vote
AuthVoteActionRevoke = "revoke" // Revoke a proposal vote authorization

// Vote option IDs
VoteOptionIDApprove = "yes"
VoteOptionIDReject = "no"

// Vote types
//
// VoteTypeStandard is used to indicate a simple approve or reject
// proposal vote where the winner is the voting option that has met
// the specified pass and quorum requirements.
//
// VoteTypeRunoff specifies a runoff vote that multiple proposals compete in.
// All proposals are voted on like normal, but there can only be one winner
// in a runoff vote. The winner is the proposal that meets the quorum
// requirement, meets the pass requirement, and that has the most net yes
// votes. The winning proposal is considered approved and all other proposals
// are considered rejected. If no proposals meet the quorum and pass
// requirements then all proposals are considered rejected.
// Note: in a runoff vote it is possible for a proposal to meet the quorum
// and pass requirements but still be rejected if it does not have the most
// net yes votes.
VoteTypeInvalid VoteT = 0
VoteTypeStandard VoteT = 1
VoteTypeRunoff VoteT = 2

// Versioning
VersionStartVoteV1 = 1
Expand Down Expand Up @@ -463,6 +481,58 @@ func DecodeStartVoteReply(payload []byte) (*StartVoteReply, error) {
return &v, nil
}

// StartVoteRunoff instructs the plugin to start a runoff vote using the given
// submissions. Each submission is required to have its own AuthorizeVote and
// StartVote.
type StartVoteRunoff struct {
Token string `json:"token"` // Token of RFP proposal
AuthorizeVotes []AuthorizeVote `json:"authorizevotes"` // Submission auth votes
StartVotes []StartVoteV2 `json:"startvotes"` // Submission start votes
}

// EncodeStartVoteRunoffencodes StartVoteRunoffinto a JSON byte slice.
func EncodeStartVoteRunoff(v StartVoteRunoff) ([]byte, error) {
return json.Marshal(v)
}

// DecodeVotedecodes a JSON byte slice into a StartVoteRunoff.
func DecodeStartVoteRunoff(payload []byte) (*StartVoteRunoff, error) {
var sv StartVoteRunoff

err := json.Unmarshal(payload, &sv)
if err != nil {
return nil, err
}

return &sv, nil
}

// StartVoteRunoffReply is the reply to StartVoteRunoff. The StartVoteReply
// will be the same for all submissions so only one is returned. The individual
// AuthorizeVoteReply is returned for each submission where the token is the
// map key.
type StartVoteRunoffReply struct {
AuthorizeVoteReplies map[string]AuthorizeVoteReply `json:"authorizevotereply"`
StartVoteReply StartVoteReply `json:"startvotereply"`
}

// EncodeStartVoteRunoffReply encodes StartVoteRunoffReply into a JSON byte slice.
func EncodeStartVoteRunoffReply(v StartVoteRunoffReply) ([]byte, error) {
return json.Marshal(v)
}

// DecodeVoteReply decodes a JSON byte slice into a StartVoteRunoffReply.
func DecodeStartVoteRunoffReply(payload []byte) (*StartVoteRunoffReply, error) {
var v StartVoteRunoffReply

err := json.Unmarshal(payload, &v)
if err != nil {
return nil, err
}

return &v, nil
}

// VoteDetails is used to retrieve the voting period details for a record.
type VoteDetails struct {
Token string `json:"token"` // Censorship token
Expand Down Expand Up @@ -554,7 +624,8 @@ func DecodeVoteResultsReply(payload []byte) (*VoteResultsReply, error) {
// VoteSummary requests a summary of a proposal vote. This includes certain
// voting period parameters and a summary of the vote results.
type VoteSummary struct {
Token string `json:"token"` // Censorship token
Token string `json:"token"` // Censorship token
BestBlock uint64 `json:"bestblock"` // Best block
}

// EncodeVoteSummary encodes VoteSummary into a JSON byte slice.
Expand Down Expand Up @@ -587,12 +658,14 @@ type VoteOptionResult struct {
// voting period parameters as well as a summary of the vote results.
type VoteSummaryReply struct {
Authorized bool `json:"authorized"` // Vote is authorized
Type VoteT `json:"type"` // Vote type
Duration uint32 `json:"duration"` // Vote duration
EndHeight string `json:"endheight"` // End block height
EligibleTicketCount int `json:"eligibleticketcount"` // Number of eligible tickets
QuorumPercentage uint32 `json:"quorumpercentage"` // Percent of eligible votes required for quorum
PassPercentage uint32 `json:"passpercentage"` // Percent of total votes required to pass
Results []VoteOptionResult `json:"results"` // Vote results
Approved bool `json:"approved"` // Was vote approved
}

// EncodeVoteSummaryReply encodes VoteSummary into a JSON byte slice.
Expand All @@ -616,7 +689,8 @@ func DecodeVoteSummaryReply(payload []byte) (*VoteSummaryReply, error) {
// includes certain voting period parameters and a summary of the vote
// results.
type BatchVoteSummary struct {
Tokens []string `json:"token"` // Censorship token
Tokens []string `json:"token"` // Censorship token
BestBlock uint64 `json:"bestblock"` // Best block
}

// EncodeBatchVoteSummary encodes BatchVoteSummary into a JSON byte slice.
Expand All @@ -638,7 +712,9 @@ func DecodeBatchVoteSummary(payload []byte) (*BatchVoteSummary, error) {

// BatchVoteSummaryReply is the reply to the VoteSummary command and returns
// certain voting period parameters as well as a summary of the vote results.
// Results will only be returned for tokens of valid vetted proposals.
// Results are returned for all tokens that correspond to a proposal. This
// includes both unvetted and vetted proposals. Tokens that do no correspond to
// a proposal are not included in the returned map.
type BatchVoteSummaryReply struct {
Summaries map[string]VoteSummaryReply `json:"summaries"` // Vote summaries
}
Expand Down Expand Up @@ -1258,3 +1334,50 @@ func DecodeLoadVoteResultsReply(payload []byte) (*LoadVoteResultsReply, error) {

return &reply, nil
}

// LinkedFrom returns a map[token][]token that contains the linked from list
// for each of the given proposal tokens. A linked from list is a list of all
// the proposals that have linked to a given proposal using the LinkTo field
// in the ProposalMetadata mdstream.
type LinkedFrom struct {
Tokens []string `json:"tokens"`
}

// EncodeLinkedFrom encodes a LinkedFrom into a JSON byte slice.
func EncodeLinkedFrom(lf LinkedFrom) ([]byte, error) {
return json.Marshal(lf)
}

// DecodeLinkedFrom decodes a JSON byte slice into a LinkedFrom.
func DecodeLinkedFrom(payload []byte) (*LinkedFrom, error) {
var lf LinkedFrom

err := json.Unmarshal(payload, &lf)
if err != nil {
return nil, err
}

return &lf, nil
}

// LinkedFromReply is the reply to the LinkedFrom command.
type LinkedFromReply struct {
LinkedFrom map[string][]string `json:"linkedfrom"`
}

// EncodeLinkedFromReply encodes a LinkedFromReply into a JSON byte slice.
func EncodeLinkedFromReply(reply LinkedFromReply) ([]byte, error) {
return json.Marshal(reply)
}

// DecodeLinkedFromReply decodes a JSON byte slice into a LinkedFrom.
func DecodeLinkedFromReply(payload []byte) (*LinkedFromReply, error) {
var reply LinkedFromReply

err := json.Unmarshal(payload, &reply)
if err != nil {
return nil, err
}

return &reply, nil
}
36 changes: 36 additions & 0 deletions mdstream/mdstream.go
Expand Up @@ -131,6 +131,42 @@ func DecodeProposalGeneralV2(payload []byte) (*ProposalGeneralV2, error) {
return &md, nil
}

// ProposalMetadata contains metadata that is specified by the user on proposal
// submission. It is attached to a proposal submission as a politeiawww
// Metadata object and is saved to politeiad as a File, not as a
// MetadataStream. The filename is defined by FilenameProposalMetadata.
//
// The reason it is saved to politeiad as a File is because politeiad only
// includes Files in the merkle root calculation. This is user defined metadata
// so it must be included in the proposal signature on submission. If it were
// saved to politeiad as a MetadataStream then it would not be included in the
// merkle root, thus causing an error where the client calculated merkle root
// if different than the politeiad calculated merkle root.
type ProposalMetadata struct {
Name string `json:"name"` // Proposal name
LinkTo string `json:"linkto,omitempty"` // Token of proposal to link to
LinkBy int64 `json:"linkby,omitempty"` // UNIX timestamp of RFP deadline
}

// EncodeProposalMetadata encodes a ProposalMetadata into a JSON byte slice.
func EncodeProposalMetadata(md ProposalMetadata) ([]byte, error) {
b, err := json.Marshal(md)
if err != nil {
return nil, err
}
return b, nil
}

// DecodeProposalMetadata decodes a JSON byte slice into a ProposalMetadata.
func DecodeProposalMetadata(payload []byte) (*ProposalMetadata, error) {
var md ProposalMetadata
err := json.Unmarshal(payload, &md)
if err != nil {
return nil, err
}
return &md, nil
}

// RecordStatusChangeV1 represents a politeiad record status change and is used
// to store additional status change metadata that would not otherwise be
// captured by the politeiad status change routes.
Expand Down