-
Notifications
You must be signed in to change notification settings - Fork 13
/
proposal.go
261 lines (227 loc) · 7.75 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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
package types
import (
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"math"
"time"
"github.com/dashpay/dashd-go/btcjson"
"github.com/dashpay/tenderdash/crypto"
"github.com/dashpay/tenderdash/internal/libs/protoio"
tmbytes "github.com/dashpay/tenderdash/libs/bytes"
tmtime "github.com/dashpay/tenderdash/libs/time"
tmproto "github.com/dashpay/tenderdash/proto/tendermint/types"
"github.com/rs/zerolog"
)
var (
ErrInvalidBlockPartSignature = errors.New("error invalid block part signature")
ErrInvalidBlockPartHash = errors.New("error invalid block part hash")
)
// Proposal defines a block proposal for the consensus.
// It refers to the block by BlockID field.
// It must be signed by the correct proposer for the given Height/Round
// to be considered valid. It may depend on votes from a previous round,
// a so-called Proof-of-Lock (POL) round, as noted in the POLRound.
// If POLRound >= 0, then BlockID corresponds to the block that is locked in POLRound.
type Proposal struct {
Type tmproto.SignedMsgType
Height int64 `json:"height,string"`
Round int32 `json:"round"` // there can not be greater than 2_147_483_647 rounds
POLRound int32 `json:"pol_round"` // -1 if null.
BlockID BlockID `json:"block_id"`
Timestamp time.Time `json:"timestamp"`
Signature []byte `json:"signature"`
// dash fields
CoreChainLockedHeight uint32 `json:"core_height"`
CoreChainLockUpdate *CoreChainLock
}
// NewProposal returns a new Proposal.
// If there is no POLRound, polRound should be -1.
func NewProposal(height int64, coreChainLockedHeight uint32, round int32, polRound int32, blockID BlockID, ts time.Time) *Proposal {
return &Proposal{
Type: tmproto.ProposalType,
Height: height,
Round: round,
BlockID: blockID,
POLRound: polRound,
Timestamp: tmtime.Canonical(ts),
CoreChainLockedHeight: coreChainLockedHeight,
}
}
// ValidateBasic performs basic validation.
func (p *Proposal) ValidateBasic() error {
if p.Type != tmproto.ProposalType {
return errors.New("invalid Type")
}
if p.Height < 0 {
return errors.New("negative Height")
}
if p.CoreChainLockedHeight == math.MaxUint32 {
return errors.New("core height not set")
}
if p.Round < 0 {
return errors.New("negative Round")
}
if p.POLRound < -1 {
return errors.New("negative POLRound (exception: -1)")
}
if err := p.BlockID.ValidateBasic(); err != nil {
return fmt.Errorf("wrong BlockID: %w", err)
}
// ValidateBasic above would pass even if the BlockID was empty:
if !p.BlockID.IsComplete() {
return fmt.Errorf("expected a complete, non-empty BlockID, got: %v", p.BlockID)
}
if p.CoreChainLockUpdate != nil {
err := p.CoreChainLockUpdate.ValidateBasic()
if err != nil {
return err
}
}
if len(p.Signature) == 0 {
return errors.New("signature is missing")
}
if len(p.Signature) > SignatureSize {
return fmt.Errorf("signature is too big (max: %d)", SignatureSize)
}
return nil
}
// IsTimely validates that the block timestamp is 'timely' according to the proposer-based timestamp algorithm.
// To evaluate if a block is timely, its timestamp is compared to the local time of the validator along with the
// configured Precision and MsgDelay parameters.
// Specifically, a proposed block timestamp is considered timely if it is satisfies the following inequalities:
//
// localtime >= proposedBlockTime - Precision
// localtime <= proposedBlockTime + MsgDelay + Precision
//
// For more information on the meaning of 'timely', see the proposer-based timestamp specification:
// https://github.com/dashpay/tenderdash/tree/master/spec/consensus/proposer-based-timestamp
//
// NOTE: by definition, at initial height, recvTime MUST be genesis time.
func (p *Proposal) IsTimely(recvTime time.Time, sp SynchronyParams, round int32) bool {
return isTimely(p.Timestamp, recvTime, sp, round)
}
// String returns a string representation of the Proposal.
//
// 1. height
// 2. round
// 3. block ID
// 4. POL round
// 5. first 6 bytes of signature
// 6. timestamp
//
// See BlockID#String.
func (p *Proposal) String() string {
if p == nil {
return "Proposal{nil}"
}
return fmt.Sprintf("Proposal{%v/%v (%v, %v) %X @ %s}",
p.Height,
p.Round,
p.BlockID,
p.POLRound,
tmbytes.Fingerprint(p.Signature),
CanonicalTime(p.Timestamp))
}
// MarshalZerologObject implements zerolog.LogObjectMarshaler
func (p *Proposal) MarshalZerologObject(e *zerolog.Event) {
if p == nil {
return
}
e.Int64("height", p.Height)
e.Int32("round", p.Round)
e.Str("block_id", p.BlockID.String())
e.Int32("pol_round", p.POLRound)
e.Str("signature", tmbytes.HexBytes(p.Signature).String())
e.Str("timestamp", CanonicalTime(p.Timestamp))
}
// SetCoreChainLockUpdate sets CoreChainLock to Proposal.CoreChainLockUpdate field
func (p *Proposal) SetCoreChainLockUpdate(coreChainLock *CoreChainLock) {
p.CoreChainLockUpdate = coreChainLock
}
// ProposalBlockSignBytes returns the proto-encoding of the canonicalized Proposal,
// for signing. Panics if the marshaling fails.
//
// The encoded Protobuf message is varint length-prefixed (using MarshalDelimited)
// for backwards-compatibility with the Amino encoding, due to e.g. hardware
// devices that rely on this encoding.
//
// See CanonicalizeProposal
func ProposalBlockSignBytes(chainID string, p *tmproto.Proposal) []byte {
pb := CanonicalizeProposal(chainID, p)
bz, err := protoio.MarshalDelimited(&pb)
if err != nil {
panic(err)
}
return bz
}
func ProposalBlockSignID(
chainID string, p *tmproto.Proposal, quorumType btcjson.LLMQType, quorumHash crypto.QuorumHash,
) []byte {
signBytes := ProposalBlockSignBytes(chainID, p)
proposalMessageHash := sha256.Sum256(signBytes)
proposalRequestID := ProposalRequestIDProto(p)
signID := NewSignItemFromHash(quorumType, quorumHash, proposalRequestID, proposalMessageHash[:]).SignHash
return signID
}
func ProposalRequestID(p *Proposal) []byte {
requestIDMessage := []byte("dpproposal")
heightByteArray := make([]byte, 8)
binary.LittleEndian.PutUint64(heightByteArray, uint64(p.Height))
roundByteArray := make([]byte, 4)
binary.LittleEndian.PutUint32(roundByteArray, uint32(p.Round))
requestIDMessage = append(requestIDMessage, heightByteArray...)
requestIDMessage = append(requestIDMessage, roundByteArray...)
hash := sha256.Sum256(requestIDMessage)
return hash[:]
}
func ProposalRequestIDProto(p *tmproto.Proposal) []byte {
requestIDMessage := []byte("dpproposal")
heightByteArray := make([]byte, 8)
binary.LittleEndian.PutUint64(heightByteArray, uint64(p.Height))
roundByteArray := make([]byte, 4)
binary.LittleEndian.PutUint32(roundByteArray, uint32(p.Round))
requestIDMessage = append(requestIDMessage, heightByteArray...)
requestIDMessage = append(requestIDMessage, roundByteArray...)
hash := sha256.Sum256(requestIDMessage)
return hash[:]
}
// ToProto converts Proposal to protobuf
func (p *Proposal) ToProto() *tmproto.Proposal {
if p == nil {
return &tmproto.Proposal{}
}
pb := new(tmproto.Proposal)
pb.BlockID = p.BlockID.ToProto()
pb.Type = p.Type
pb.Height = p.Height
pb.CoreChainLockedHeight = p.CoreChainLockedHeight
pb.CoreChainLockUpdate = p.CoreChainLockUpdate.ToProto()
pb.Round = p.Round
pb.PolRound = p.POLRound
pb.Timestamp = p.Timestamp
pb.Signature = p.Signature
return pb
}
// FromProto sets a protobuf Proposal to the given pointer.
// It returns an error if the proposal is invalid.
func ProposalFromProto(pp *tmproto.Proposal) (*Proposal, error) {
if pp == nil {
return nil, errors.New("nil proposal")
}
p := new(Proposal)
blockID, err := BlockIDFromProto(&pp.BlockID)
if err != nil {
return nil, err
}
p.BlockID = *blockID
p.Type = pp.Type
p.Height = pp.Height
p.CoreChainLockedHeight = pp.CoreChainLockedHeight
p.Round = pp.Round
p.POLRound = pp.PolRound
p.Timestamp = pp.Timestamp
p.Signature = pp.Signature
return p, p.ValidateBasic()
}