-
Notifications
You must be signed in to change notification settings - Fork 211
/
proposal.go
188 lines (158 loc) · 5.71 KB
/
proposal.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package types
import (
"bytes"
"fmt"
"sort"
"github.com/google/go-cmp/cmp"
"github.com/spacemeshos/go-scale"
"github.com/spacemeshos/go-spacemesh/codec"
"github.com/spacemeshos/go-spacemesh/hash"
"github.com/spacemeshos/go-spacemesh/log"
)
const (
// ProposalIDSize in bytes.
// FIXME(dshulyak) why do we cast to hash32 when returning bytes?
// probably required for fetching by hash between peers.
ProposalIDSize = Hash32Length
)
//go:generate scalegen
// ProposalID is a 20-byte blake3 sum of the serialized ballot used to identify a Proposal.
type ProposalID Hash20
// EmptyProposalID is a canonical empty ProposalID.
var EmptyProposalID = ProposalID{}
// EncodeScale implements scale codec interface.
func (id *ProposalID) EncodeScale(e *scale.Encoder) (int, error) {
return scale.EncodeByteArray(e, id[:])
}
// DecodeScale implements scale codec interface.
func (id *ProposalID) DecodeScale(d *scale.Decoder) (int, error) {
return scale.DecodeByteArray(d, id[:])
}
// Proposal contains the smesher's signed content proposal for a given layer and vote on the mesh history.
// Proposal is ephemeral and will be discarded after the unified content block is created. the Ballot within
// the Proposal will remain in the mesh.
type Proposal struct {
// the content proposal for a given layer and the votes on the mesh history
InnerProposal
// the smesher's signature on the InnerProposal
Signature EdSignature
// the following fields are kept private and from being serialized
proposalID ProposalID
}
func (p Proposal) Equal(other Proposal) bool {
return cmp.Equal(p.InnerProposal, other.InnerProposal) && p.Signature == other.Signature
}
// InnerProposal contains a smesher's content proposal for layer and its votes on the mesh history.
// this structure is serialized and signed to produce the signature in Proposal.
type InnerProposal struct {
// smesher's votes on the mesh history
Ballot
// smesher's content proposal for a layer
TxIDs []TransactionID `scale:"max=100000"`
// aggregated hash up to the layer before this proposal.
MeshHash Hash32
// TODO add this when a state commitment mechanism is implemented.
// state root up to the layer before this proposal.
// note: this is needed in addition to mesh hash to detect bug in SVM
// StateHash Hash32
}
// Initialize calculates and sets the Proposal's cached proposalID.
// this should be called once all the other fields of the Proposal are set.
func (p *Proposal) Initialize() error {
if p.proposalID != EmptyProposalID {
return fmt.Errorf("proposal already initialized")
}
if err := p.Ballot.Initialize(); err != nil {
return err
}
h := hash.Sum(p.SignedBytes())
p.proposalID = ProposalID(Hash32(h).ToHash20())
return nil
}
func (p *Proposal) MustInitialize() {
if err := p.Initialize(); err != nil {
panic(err)
}
}
// SignedBytes returns the serialization of the InnerProposal.
func (p *Proposal) SignedBytes() []byte {
data, err := codec.Encode(&BallotMetadata{
Layer: p.Layer,
MsgHash: BytesToHash(p.HashInnerProposal()),
})
if err != nil {
log.With().Fatal("failed to serialize BallotMetadata for proposal", log.Err(err))
}
return data
}
// HashInnerProposal returns the hash of the InnerProposal.
func (p *Proposal) HashInnerProposal() []byte {
h := hash.New()
_, err := codec.EncodeTo(h, &p.InnerProposal)
if err != nil {
log.With().Fatal("failed to encode InnerProposal for hashing", log.Err(err))
}
return h.Sum(nil)
}
// ID returns the ProposalID.
func (p *Proposal) ID() ProposalID {
return p.proposalID
}
// SetID set the ProposalID.
func (p *Proposal) SetID(pid ProposalID) {
p.proposalID = pid
}
// MarshalLogObject implements logging interface.
func (p *Proposal) MarshalLogObject(encoder log.ObjectEncoder) error {
encoder.AddString("proposal_id", p.ID().String())
encoder.AddInt("transactions", len(p.TxIDs))
encoder.AddString("mesh_hash", p.MeshHash.ShortString())
p.Ballot.MarshalLogObject(encoder)
return nil
}
// String returns a short prefix of the hex representation of the ID.
func (id ProposalID) String() string {
return id.AsHash32().ShortString()
}
// Bytes returns the ProposalID as a byte slice.
func (id ProposalID) Bytes() []byte {
return id.AsHash32().Bytes()
}
// AsHash32 returns a Hash32 whose first 20 bytes are the bytes of this ProposalID, it is right-padded with zeros.
func (id ProposalID) AsHash32() Hash32 {
return Hash20(id).ToHash32()
}
// Field returns a log field. Implements the LoggableField interface.
func (id ProposalID) Field() log.Field {
return log.String("proposal_id", id.String())
}
// Compare returns true if other (the given ProposalID) is less than this ProposalID, by lexicographic comparison.
func (id ProposalID) Compare(other ProposalID) bool {
return bytes.Compare(id.Bytes(), other.Bytes()) < 0
}
// ToProposalIDs returns a slice of ProposalID corresponding to the given proposals.
func ToProposalIDs(proposals []*Proposal) []ProposalID {
ids := make([]ProposalID, 0, len(proposals))
for _, p := range proposals {
ids = append(ids, p.ID())
}
return ids
}
// SortProposals sorts a list of Proposal in their ID's lexicographic order, in-place.
func SortProposals(proposals []*Proposal) []*Proposal {
sort.Slice(proposals, func(i, j int) bool { return proposals[i].ID().Compare(proposals[j].ID()) })
return proposals
}
// SortProposalIDs sorts a list of ProposalID in lexicographic order, in-place.
func SortProposalIDs(ids []ProposalID) []ProposalID {
sort.Slice(ids, func(i, j int) bool { return ids[i].Compare(ids[j]) })
return ids
}
// ProposalIDsToHashes turns a list of ProposalID into their Hash32 representation.
func ProposalIDsToHashes(ids []ProposalID) []Hash32 {
hashes := make([]Hash32, 0, len(ids))
for _, id := range ids {
hashes = append(hashes, id.AsHash32())
}
return hashes
}