github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/types/proposal_test.go (about) 1 package types 2 3 import ( 4 "context" 5 "math" 6 "testing" 7 "time" 8 9 "github.com/ari-anchor/sei-tendermint/version" 10 11 "github.com/gogo/protobuf/proto" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 15 "github.com/ari-anchor/sei-tendermint/crypto" 16 "github.com/ari-anchor/sei-tendermint/internal/libs/protoio" 17 tmrand "github.com/ari-anchor/sei-tendermint/libs/rand" 18 tmtime "github.com/ari-anchor/sei-tendermint/libs/time" 19 tmproto "github.com/ari-anchor/sei-tendermint/proto/tendermint/types" 20 ) 21 22 func generateHeader() Header { 23 return Header{ 24 Version: version.Consensus{Block: version.BlockProtocol}, 25 ChainID: string(make([]byte, MaxChainIDLen)), 26 Height: 1, 27 LastBlockID: BlockID{ 28 Hash: make([]byte, crypto.HashSize), 29 PartSetHeader: PartSetHeader{ 30 Hash: make([]byte, crypto.HashSize), 31 }, 32 }, 33 LastCommitHash: make([]byte, crypto.HashSize), 34 DataHash: make([]byte, crypto.HashSize), 35 EvidenceHash: make([]byte, crypto.HashSize), 36 ProposerAddress: make([]byte, crypto.AddressSize), 37 ValidatorsHash: make([]byte, crypto.HashSize), 38 NextValidatorsHash: make([]byte, crypto.HashSize), 39 ConsensusHash: make([]byte, crypto.HashSize), 40 LastResultsHash: make([]byte, crypto.HashSize), 41 } 42 } 43 func getTestProposal(t testing.TB) *Proposal { 44 t.Helper() 45 46 stamp, err := time.Parse(TimeFormat, "2018-02-11T07:09:22.765Z") 47 require.NoError(t, err) 48 49 return &Proposal{ 50 Height: 12345, 51 Round: 23456, 52 BlockID: BlockID{Hash: []byte("--June_15_2020_amino_was_removed"), 53 PartSetHeader: PartSetHeader{Total: 111, Hash: []byte("--June_15_2020_amino_was_removed")}}, 54 POLRound: -1, 55 Timestamp: stamp, 56 } 57 } 58 59 func TestProposalSignable(t *testing.T) { 60 chainID := "test_chain_id" 61 signBytes := ProposalSignBytes(chainID, getTestProposal(t).ToProto()) 62 pb := CanonicalizeProposal(chainID, getTestProposal(t).ToProto()) 63 64 expected, err := protoio.MarshalDelimited(&pb) 65 require.NoError(t, err) 66 require.Equal(t, expected, signBytes, "Got unexpected sign bytes for Proposal") 67 } 68 69 func TestProposalString(t *testing.T) { 70 str := getTestProposal(t).String() 71 expected := `Proposal{12345/23456 (2D2D4A756E655F31355F323032305F616D696E6F5F7761735F72656D6F766564:111:2D2D4A756E65, -1) 000000000000 @ 2018-02-11T07:09:22.765Z}` 72 if str != expected { 73 t.Errorf("got unexpected string for Proposal. Expected:\n%v\nGot:\n%v", expected, str) 74 } 75 } 76 77 func TestProposalVerifySignature(t *testing.T) { 78 ctx, cancel := context.WithCancel(context.Background()) 79 defer cancel() 80 81 privVal := NewMockPV() 82 pubKey, err := privVal.GetPubKey(ctx) 83 require.NoError(t, err) 84 85 txKeys := make([]TxKey, 0) 86 prop := NewProposal( 87 4, 2, 2, 88 BlockID{tmrand.Bytes(crypto.HashSize), PartSetHeader{777, tmrand.Bytes(crypto.HashSize)}}, tmtime.Now(), txKeys, generateHeader(), &Commit{}, EvidenceList{}, pubKey.Address()) 89 p := prop.ToProto() 90 signBytes := ProposalSignBytes("test_chain_id", p) 91 92 // sign it 93 err = privVal.SignProposal(ctx, "test_chain_id", p) 94 require.NoError(t, err) 95 prop.Signature = p.Signature 96 97 // verify the same proposal 98 valid := pubKey.VerifySignature(signBytes, prop.Signature) 99 require.True(t, valid) 100 101 // serialize, deserialize and verify again.... 102 newProp := new(tmproto.Proposal) 103 pb := prop.ToProto() 104 105 bs, err := proto.Marshal(pb) 106 require.NoError(t, err) 107 108 err = proto.Unmarshal(bs, newProp) 109 require.NoError(t, err) 110 111 np, err := ProposalFromProto(newProp) 112 require.NoError(t, err) 113 114 // verify the transmitted proposal 115 newSignBytes := ProposalSignBytes("test_chain_id", pb) 116 require.Equal(t, string(signBytes), string(newSignBytes)) 117 valid = pubKey.VerifySignature(newSignBytes, np.Signature) 118 require.True(t, valid) 119 } 120 121 func BenchmarkProposalWriteSignBytes(b *testing.B) { 122 pbp := getTestProposal(b).ToProto() 123 124 b.ResetTimer() 125 126 for i := 0; i < b.N; i++ { 127 ProposalSignBytes("test_chain_id", pbp) 128 } 129 } 130 131 func BenchmarkProposalSign(b *testing.B) { 132 ctx, cancel := context.WithCancel(context.Background()) 133 defer cancel() 134 135 privVal := NewMockPV() 136 137 pbp := getTestProposal(b).ToProto() 138 b.ResetTimer() 139 140 for i := 0; i < b.N; i++ { 141 err := privVal.SignProposal(ctx, "test_chain_id", pbp) 142 if err != nil { 143 b.Error(err) 144 } 145 } 146 } 147 148 func BenchmarkProposalVerifySignature(b *testing.B) { 149 testProposal := getTestProposal(b) 150 pbp := testProposal.ToProto() 151 ctx, cancel := context.WithCancel(context.Background()) 152 defer cancel() 153 154 privVal := NewMockPV() 155 err := privVal.SignProposal(ctx, "test_chain_id", pbp) 156 require.NoError(b, err) 157 pubKey, err := privVal.GetPubKey(ctx) 158 require.NoError(b, err) 159 160 b.ResetTimer() 161 162 for i := 0; i < b.N; i++ { 163 pubKey.VerifySignature(ProposalSignBytes("test_chain_id", pbp), testProposal.Signature) 164 } 165 } 166 167 func TestProposalValidateBasic(t *testing.T) { 168 169 privVal := NewMockPV() 170 testCases := []struct { 171 testName string 172 malleateProposal func(*Proposal) 173 expectErr bool 174 }{ 175 {"Good Proposal", func(p *Proposal) {}, false}, 176 {"Invalid Type", func(p *Proposal) { p.Type = tmproto.PrecommitType }, true}, 177 {"Invalid Height", func(p *Proposal) { p.Height = -1 }, true}, 178 {"Invalid Round", func(p *Proposal) { p.Round = -1 }, true}, 179 {"Invalid POLRound", func(p *Proposal) { p.POLRound = -2 }, true}, 180 {"Invalid BlockId", func(p *Proposal) { 181 p.BlockID = BlockID{[]byte{1, 2, 3}, PartSetHeader{111, []byte("blockparts")}} 182 }, true}, 183 {"Invalid Signature", func(p *Proposal) { 184 p.Signature = make([]byte, 0) 185 }, true}, 186 {"Too big Signature", func(p *Proposal) { 187 p.Signature = make([]byte, MaxSignatureSize+1) 188 }, true}, 189 } 190 blockID := makeBlockID(crypto.Checksum([]byte("blockhash")), math.MaxInt32, crypto.Checksum([]byte("partshash"))) 191 192 for _, tc := range testCases { 193 tc := tc 194 t.Run(tc.testName, func(t *testing.T) { 195 ctx, cancel := context.WithCancel(context.Background()) 196 defer cancel() 197 198 txKeys := make([]TxKey, 0) 199 pubKey, err := privVal.GetPubKey(ctx) 200 require.NoError(t, err) 201 prop := NewProposal( 202 4, 2, 2, 203 blockID, tmtime.Now(), txKeys, 204 generateHeader(), &Commit{}, EvidenceList{}, pubKey.Address()) 205 p := prop.ToProto() 206 err = privVal.SignProposal(ctx, "test_chain_id", p) 207 prop.Signature = p.Signature 208 require.NoError(t, err) 209 tc.malleateProposal(prop) 210 assert.Equal(t, tc.expectErr, prop.ValidateBasic() != nil, "Validate Basic had an unexpected result") 211 }) 212 } 213 } 214 215 func TestProposalProtoBuf(t *testing.T) { 216 var txKeys []TxKey 217 proposal := NewProposal(1, 2, 3, makeBlockID([]byte("hash"), 2, []byte("part_set_hash")), tmtime.Now(), txKeys, generateHeader(), &Commit{Signatures: []CommitSig{}}, EvidenceList{}, crypto.Address("testaddr")) 218 proposal.Signature = []byte("sig") 219 proposal2 := NewProposal(1, 2, 3, BlockID{}, tmtime.Now(), txKeys, generateHeader(), &Commit{Signatures: []CommitSig{}}, EvidenceList{}, crypto.Address("testaddr")) 220 221 testCases := []struct { 222 msg string 223 p1 *Proposal 224 expPass bool 225 }{ 226 {"success", proposal, true}, 227 {"success", proposal2, false}, // blcokID cannot be empty 228 {"empty proposal failure validatebasic", &Proposal{}, false}, 229 {"nil proposal", nil, false}, 230 } 231 for _, tc := range testCases { 232 protoProposal := tc.p1.ToProto() 233 234 p, err := ProposalFromProto(protoProposal) 235 if tc.expPass { 236 require.NoError(t, err) 237 require.Equal(t, tc.p1, p, tc.msg) 238 } else { 239 require.Error(t, err) 240 } 241 } 242 } 243 244 func TestIsTimely(t *testing.T) { 245 genesisTime, err := time.Parse(time.RFC3339, "2019-03-13T23:00:00Z") 246 require.NoError(t, err) 247 testCases := []struct { 248 name string 249 proposalTime time.Time 250 recvTime time.Time 251 precision time.Duration 252 msgDelay time.Duration 253 expectTimely bool 254 round int32 255 }{ 256 // proposalTime - precision <= localTime <= proposalTime + msgDelay + precision 257 { 258 // Checking that the following inequality evaluates to true: 259 // 0 - 2 <= 1 <= 0 + 1 + 2 260 name: "basic timely", 261 proposalTime: genesisTime, 262 recvTime: genesisTime.Add(1 * time.Nanosecond), 263 precision: time.Nanosecond * 2, 264 msgDelay: time.Nanosecond, 265 expectTimely: true, 266 }, 267 { 268 // Checking that the following inequality evaluates to false: 269 // 0 - 2 <= 4 <= 0 + 1 + 2 270 name: "local time too large", 271 proposalTime: genesisTime, 272 recvTime: genesisTime.Add(4 * time.Nanosecond), 273 precision: time.Nanosecond * 2, 274 msgDelay: time.Nanosecond, 275 expectTimely: false, 276 }, 277 { 278 // Checking that the following inequality evaluates to false: 279 // 4 - 2 <= 0 <= 4 + 2 + 1 280 name: "proposal time too large", 281 proposalTime: genesisTime.Add(4 * time.Nanosecond), 282 recvTime: genesisTime, 283 precision: time.Nanosecond * 2, 284 msgDelay: time.Nanosecond, 285 expectTimely: false, 286 }, 287 { 288 // Checking that the following inequality evaluates to true: 289 // 0 - (2 * 2) <= 4 <= 0 + (1 * 2) + 2 290 name: "message delay adapts after 10 rounds", 291 proposalTime: genesisTime, 292 recvTime: genesisTime.Add(4 * time.Nanosecond), 293 precision: time.Nanosecond * 2, 294 msgDelay: time.Nanosecond, 295 expectTimely: true, 296 round: 10, 297 }, 298 { 299 // check that values that overflow time.Duration still correctly register 300 // as timely when round relaxation applied. 301 name: "message delay fixed to not overflow time.Duration", 302 proposalTime: genesisTime, 303 recvTime: genesisTime.Add(4 * time.Nanosecond), 304 precision: time.Nanosecond * 2, 305 msgDelay: time.Nanosecond, 306 expectTimely: true, 307 round: 5000, 308 }, 309 } 310 311 for _, testCase := range testCases { 312 t.Run(testCase.name, func(t *testing.T) { 313 p := Proposal{ 314 Timestamp: testCase.proposalTime, 315 } 316 317 sp := SynchronyParams{ 318 Precision: testCase.precision, 319 MessageDelay: testCase.msgDelay, 320 } 321 322 ti := p.IsTimely(testCase.recvTime, sp, testCase.round) 323 assert.Equal(t, testCase.expectTimely, ti) 324 }) 325 } 326 }