bitbucket.org/number571/tendermint@v0.8.14/types/vote_set_test.go (about)

     1  package types
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"sort"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"bitbucket.org/number571/tendermint/crypto"
    13  	tmrand "bitbucket.org/number571/tendermint/libs/rand"
    14  	tmtime "bitbucket.org/number571/tendermint/libs/time"
    15  	tmproto "bitbucket.org/number571/tendermint/proto/tendermint/types"
    16  )
    17  
    18  func TestVoteSet_AddVote_Good(t *testing.T) {
    19  	height, round := int64(1), int32(0)
    20  	voteSet, _, privValidators := randVoteSet(height, round, tmproto.PrevoteType, 10, 1)
    21  	val0 := privValidators[0]
    22  
    23  	val0p, err := val0.GetPubKey(context.Background())
    24  	require.NoError(t, err)
    25  	val0Addr := val0p.Address()
    26  
    27  	assert.Nil(t, voteSet.GetByAddress(val0Addr))
    28  	assert.False(t, voteSet.BitArray().GetIndex(0))
    29  	blockID, ok := voteSet.TwoThirdsMajority()
    30  	assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority")
    31  
    32  	vote := &Vote{
    33  		ValidatorAddress: val0Addr,
    34  		ValidatorIndex:   0, // since privValidators are in order
    35  		Height:           height,
    36  		Round:            round,
    37  		Type:             tmproto.PrevoteType,
    38  		Timestamp:        tmtime.Now(),
    39  		BlockID:          BlockID{nil, PartSetHeader{}},
    40  	}
    41  	_, err = signAddVote(val0, vote, voteSet)
    42  	require.NoError(t, err)
    43  
    44  	assert.NotNil(t, voteSet.GetByAddress(val0Addr))
    45  	assert.True(t, voteSet.BitArray().GetIndex(0))
    46  	blockID, ok = voteSet.TwoThirdsMajority()
    47  	assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority")
    48  }
    49  
    50  func TestVoteSet_AddVote_Bad(t *testing.T) {
    51  	height, round := int64(1), int32(0)
    52  	voteSet, _, privValidators := randVoteSet(height, round, tmproto.PrevoteType, 10, 1)
    53  
    54  	voteProto := &Vote{
    55  		ValidatorAddress: nil,
    56  		ValidatorIndex:   -1,
    57  		Height:           height,
    58  		Round:            round,
    59  		Timestamp:        tmtime.Now(),
    60  		Type:             tmproto.PrevoteType,
    61  		BlockID:          BlockID{nil, PartSetHeader{}},
    62  	}
    63  
    64  	// val0 votes for nil.
    65  	{
    66  		pubKey, err := privValidators[0].GetPubKey(context.Background())
    67  		require.NoError(t, err)
    68  		addr := pubKey.Address()
    69  		vote := withValidator(voteProto, addr, 0)
    70  		added, err := signAddVote(privValidators[0], vote, voteSet)
    71  		if !added || err != nil {
    72  			t.Errorf("expected VoteSet.Add to succeed")
    73  		}
    74  	}
    75  
    76  	// val0 votes again for some block.
    77  	{
    78  		pubKey, err := privValidators[0].GetPubKey(context.Background())
    79  		require.NoError(t, err)
    80  		addr := pubKey.Address()
    81  		vote := withValidator(voteProto, addr, 0)
    82  		added, err := signAddVote(privValidators[0], withBlockHash(vote, tmrand.Bytes(32)), voteSet)
    83  		if added || err == nil {
    84  			t.Errorf("expected VoteSet.Add to fail, conflicting vote.")
    85  		}
    86  	}
    87  
    88  	// val1 votes on another height
    89  	{
    90  		pubKey, err := privValidators[1].GetPubKey(context.Background())
    91  		require.NoError(t, err)
    92  		addr := pubKey.Address()
    93  		vote := withValidator(voteProto, addr, 1)
    94  		added, err := signAddVote(privValidators[1], withHeight(vote, height+1), voteSet)
    95  		if added || err == nil {
    96  			t.Errorf("expected VoteSet.Add to fail, wrong height")
    97  		}
    98  	}
    99  
   100  	// val2 votes on another round
   101  	{
   102  		pubKey, err := privValidators[2].GetPubKey(context.Background())
   103  		require.NoError(t, err)
   104  		addr := pubKey.Address()
   105  		vote := withValidator(voteProto, addr, 2)
   106  		added, err := signAddVote(privValidators[2], withRound(vote, round+1), voteSet)
   107  		if added || err == nil {
   108  			t.Errorf("expected VoteSet.Add to fail, wrong round")
   109  		}
   110  	}
   111  
   112  	// val3 votes of another type.
   113  	{
   114  		pubKey, err := privValidators[3].GetPubKey(context.Background())
   115  		require.NoError(t, err)
   116  		addr := pubKey.Address()
   117  		vote := withValidator(voteProto, addr, 3)
   118  		added, err := signAddVote(privValidators[3], withType(vote, byte(tmproto.PrecommitType)), voteSet)
   119  		if added || err == nil {
   120  			t.Errorf("expected VoteSet.Add to fail, wrong type")
   121  		}
   122  	}
   123  }
   124  
   125  func TestVoteSet_2_3Majority(t *testing.T) {
   126  	height, round := int64(1), int32(0)
   127  	voteSet, _, privValidators := randVoteSet(height, round, tmproto.PrevoteType, 10, 1)
   128  
   129  	voteProto := &Vote{
   130  		ValidatorAddress: nil, // NOTE: must fill in
   131  		ValidatorIndex:   -1,  // NOTE: must fill in
   132  		Height:           height,
   133  		Round:            round,
   134  		Type:             tmproto.PrevoteType,
   135  		Timestamp:        tmtime.Now(),
   136  		BlockID:          BlockID{nil, PartSetHeader{}},
   137  	}
   138  	// 6 out of 10 voted for nil.
   139  	for i := int32(0); i < 6; i++ {
   140  		pubKey, err := privValidators[i].GetPubKey(context.Background())
   141  		require.NoError(t, err)
   142  		addr := pubKey.Address()
   143  		vote := withValidator(voteProto, addr, i)
   144  		_, err = signAddVote(privValidators[i], vote, voteSet)
   145  		require.NoError(t, err)
   146  	}
   147  	blockID, ok := voteSet.TwoThirdsMajority()
   148  	assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority")
   149  
   150  	// 7th validator voted for some blockhash
   151  	{
   152  		pubKey, err := privValidators[6].GetPubKey(context.Background())
   153  		require.NoError(t, err)
   154  		addr := pubKey.Address()
   155  		vote := withValidator(voteProto, addr, 6)
   156  		_, err = signAddVote(privValidators[6], withBlockHash(vote, tmrand.Bytes(32)), voteSet)
   157  		require.NoError(t, err)
   158  		blockID, ok = voteSet.TwoThirdsMajority()
   159  		assert.False(t, ok || !blockID.IsZero(), "there should be no 2/3 majority")
   160  	}
   161  
   162  	// 8th validator voted for nil.
   163  	{
   164  		pubKey, err := privValidators[7].GetPubKey(context.Background())
   165  		require.NoError(t, err)
   166  		addr := pubKey.Address()
   167  		vote := withValidator(voteProto, addr, 7)
   168  		_, err = signAddVote(privValidators[7], vote, voteSet)
   169  		require.NoError(t, err)
   170  		blockID, ok = voteSet.TwoThirdsMajority()
   171  		assert.True(t, ok || blockID.IsZero(), "there should be 2/3 majority for nil")
   172  	}
   173  }
   174  
   175  func TestVoteSet_2_3MajorityRedux(t *testing.T) {
   176  	height, round := int64(1), int32(0)
   177  	voteSet, _, privValidators := randVoteSet(height, round, tmproto.PrevoteType, 100, 1)
   178  
   179  	blockHash := crypto.CRandBytes(32)
   180  	blockPartsTotal := uint32(123)
   181  	blockPartSetHeader := PartSetHeader{blockPartsTotal, crypto.CRandBytes(32)}
   182  
   183  	voteProto := &Vote{
   184  		ValidatorAddress: nil, // NOTE: must fill in
   185  		ValidatorIndex:   -1,  // NOTE: must fill in
   186  		Height:           height,
   187  		Round:            round,
   188  		Timestamp:        tmtime.Now(),
   189  		Type:             tmproto.PrevoteType,
   190  		BlockID:          BlockID{blockHash, blockPartSetHeader},
   191  	}
   192  
   193  	// 66 out of 100 voted for nil.
   194  	for i := int32(0); i < 66; i++ {
   195  		pubKey, err := privValidators[i].GetPubKey(context.Background())
   196  		require.NoError(t, err)
   197  		addr := pubKey.Address()
   198  		vote := withValidator(voteProto, addr, i)
   199  		_, err = signAddVote(privValidators[i], vote, voteSet)
   200  		require.NoError(t, err)
   201  	}
   202  	blockID, ok := voteSet.TwoThirdsMajority()
   203  	assert.False(t, ok || !blockID.IsZero(),
   204  		"there should be no 2/3 majority")
   205  
   206  	// 67th validator voted for nil
   207  	{
   208  		pubKey, err := privValidators[66].GetPubKey(context.Background())
   209  		require.NoError(t, err)
   210  		adrr := pubKey.Address()
   211  		vote := withValidator(voteProto, adrr, 66)
   212  		_, err = signAddVote(privValidators[66], withBlockHash(vote, nil), voteSet)
   213  		require.NoError(t, err)
   214  		blockID, ok = voteSet.TwoThirdsMajority()
   215  		assert.False(t, ok || !blockID.IsZero(),
   216  			"there should be no 2/3 majority: last vote added was nil")
   217  	}
   218  
   219  	// 68th validator voted for a different BlockParts PartSetHeader
   220  	{
   221  		pubKey, err := privValidators[67].GetPubKey(context.Background())
   222  		require.NoError(t, err)
   223  		addr := pubKey.Address()
   224  		vote := withValidator(voteProto, addr, 67)
   225  		blockPartsHeader := PartSetHeader{blockPartsTotal, crypto.CRandBytes(32)}
   226  		_, err = signAddVote(privValidators[67], withBlockPartSetHeader(vote, blockPartsHeader), voteSet)
   227  		require.NoError(t, err)
   228  		blockID, ok = voteSet.TwoThirdsMajority()
   229  		assert.False(t, ok || !blockID.IsZero(),
   230  			"there should be no 2/3 majority: last vote added had different PartSetHeader Hash")
   231  	}
   232  
   233  	// 69th validator voted for different BlockParts Total
   234  	{
   235  		pubKey, err := privValidators[68].GetPubKey(context.Background())
   236  		require.NoError(t, err)
   237  		addr := pubKey.Address()
   238  		vote := withValidator(voteProto, addr, 68)
   239  		blockPartsHeader := PartSetHeader{blockPartsTotal + 1, blockPartSetHeader.Hash}
   240  		_, err = signAddVote(privValidators[68], withBlockPartSetHeader(vote, blockPartsHeader), voteSet)
   241  		require.NoError(t, err)
   242  		blockID, ok = voteSet.TwoThirdsMajority()
   243  		assert.False(t, ok || !blockID.IsZero(),
   244  			"there should be no 2/3 majority: last vote added had different PartSetHeader Total")
   245  	}
   246  
   247  	// 70th validator voted for different BlockHash
   248  	{
   249  		pubKey, err := privValidators[69].GetPubKey(context.Background())
   250  		require.NoError(t, err)
   251  		addr := pubKey.Address()
   252  		vote := withValidator(voteProto, addr, 69)
   253  		_, err = signAddVote(privValidators[69], withBlockHash(vote, tmrand.Bytes(32)), voteSet)
   254  		require.NoError(t, err)
   255  		blockID, ok = voteSet.TwoThirdsMajority()
   256  		assert.False(t, ok || !blockID.IsZero(),
   257  			"there should be no 2/3 majority: last vote added had different BlockHash")
   258  	}
   259  
   260  	// 71st validator voted for the right BlockHash & BlockPartSetHeader
   261  	{
   262  		pubKey, err := privValidators[70].GetPubKey(context.Background())
   263  		require.NoError(t, err)
   264  		addr := pubKey.Address()
   265  		vote := withValidator(voteProto, addr, 70)
   266  		_, err = signAddVote(privValidators[70], vote, voteSet)
   267  		require.NoError(t, err)
   268  		blockID, ok = voteSet.TwoThirdsMajority()
   269  		assert.True(t, ok && blockID.Equals(BlockID{blockHash, blockPartSetHeader}),
   270  			"there should be 2/3 majority")
   271  	}
   272  }
   273  
   274  func TestVoteSet_Conflicts(t *testing.T) {
   275  	height, round := int64(1), int32(0)
   276  	voteSet, _, privValidators := randVoteSet(height, round, tmproto.PrevoteType, 4, 1)
   277  	blockHash1 := tmrand.Bytes(32)
   278  	blockHash2 := tmrand.Bytes(32)
   279  
   280  	voteProto := &Vote{
   281  		ValidatorAddress: nil,
   282  		ValidatorIndex:   -1,
   283  		Height:           height,
   284  		Round:            round,
   285  		Timestamp:        tmtime.Now(),
   286  		Type:             tmproto.PrevoteType,
   287  		BlockID:          BlockID{nil, PartSetHeader{}},
   288  	}
   289  
   290  	val0, err := privValidators[0].GetPubKey(context.Background())
   291  	require.NoError(t, err)
   292  	val0Addr := val0.Address()
   293  
   294  	// val0 votes for nil.
   295  	{
   296  		vote := withValidator(voteProto, val0Addr, 0)
   297  		added, err := signAddVote(privValidators[0], vote, voteSet)
   298  		if !added || err != nil {
   299  			t.Errorf("expected VoteSet.Add to succeed")
   300  		}
   301  	}
   302  
   303  	// val0 votes again for blockHash1.
   304  	{
   305  		vote := withValidator(voteProto, val0Addr, 0)
   306  		added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash1), voteSet)
   307  		assert.False(t, added, "conflicting vote")
   308  		assert.Error(t, err, "conflicting vote")
   309  	}
   310  
   311  	// start tracking blockHash1
   312  	err = voteSet.SetPeerMaj23("peerA", BlockID{blockHash1, PartSetHeader{}})
   313  	require.NoError(t, err)
   314  
   315  	// val0 votes again for blockHash1.
   316  	{
   317  		vote := withValidator(voteProto, val0Addr, 0)
   318  		added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash1), voteSet)
   319  		assert.True(t, added, "called SetPeerMaj23()")
   320  		assert.Error(t, err, "conflicting vote")
   321  	}
   322  
   323  	// attempt tracking blockHash2, should fail because already set for peerA.
   324  	err = voteSet.SetPeerMaj23("peerA", BlockID{blockHash2, PartSetHeader{}})
   325  	require.Error(t, err)
   326  
   327  	// val0 votes again for blockHash1.
   328  	{
   329  		vote := withValidator(voteProto, val0Addr, 0)
   330  		added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash2), voteSet)
   331  		assert.False(t, added, "duplicate SetPeerMaj23() from peerA")
   332  		assert.Error(t, err, "conflicting vote")
   333  	}
   334  
   335  	// val1 votes for blockHash1.
   336  	{
   337  		pv, err := privValidators[1].GetPubKey(context.Background())
   338  		assert.NoError(t, err)
   339  		addr := pv.Address()
   340  		vote := withValidator(voteProto, addr, 1)
   341  		added, err := signAddVote(privValidators[1], withBlockHash(vote, blockHash1), voteSet)
   342  		if !added || err != nil {
   343  			t.Errorf("expected VoteSet.Add to succeed")
   344  		}
   345  	}
   346  
   347  	// check
   348  	if voteSet.HasTwoThirdsMajority() {
   349  		t.Errorf("we shouldn't have 2/3 majority yet")
   350  	}
   351  	if voteSet.HasTwoThirdsAny() {
   352  		t.Errorf("we shouldn't have 2/3 if any votes yet")
   353  	}
   354  
   355  	// val2 votes for blockHash2.
   356  	{
   357  		pv, err := privValidators[2].GetPubKey(context.Background())
   358  		assert.NoError(t, err)
   359  		addr := pv.Address()
   360  		vote := withValidator(voteProto, addr, 2)
   361  		added, err := signAddVote(privValidators[2], withBlockHash(vote, blockHash2), voteSet)
   362  		if !added || err != nil {
   363  			t.Errorf("expected VoteSet.Add to succeed")
   364  		}
   365  	}
   366  
   367  	// check
   368  	if voteSet.HasTwoThirdsMajority() {
   369  		t.Errorf("we shouldn't have 2/3 majority yet")
   370  	}
   371  	if !voteSet.HasTwoThirdsAny() {
   372  		t.Errorf("we should have 2/3 if any votes")
   373  	}
   374  
   375  	// now attempt tracking blockHash1
   376  	err = voteSet.SetPeerMaj23("peerB", BlockID{blockHash1, PartSetHeader{}})
   377  	require.NoError(t, err)
   378  
   379  	// val2 votes for blockHash1.
   380  	{
   381  		pv, err := privValidators[2].GetPubKey(context.Background())
   382  		assert.NoError(t, err)
   383  		addr := pv.Address()
   384  		vote := withValidator(voteProto, addr, 2)
   385  		added, err := signAddVote(privValidators[2], withBlockHash(vote, blockHash1), voteSet)
   386  		assert.True(t, added)
   387  		assert.Error(t, err, "conflicting vote")
   388  	}
   389  
   390  	// check
   391  	if !voteSet.HasTwoThirdsMajority() {
   392  		t.Errorf("we should have 2/3 majority for blockHash1")
   393  	}
   394  	blockIDMaj23, _ := voteSet.TwoThirdsMajority()
   395  	if !bytes.Equal(blockIDMaj23.Hash, blockHash1) {
   396  		t.Errorf("got the wrong 2/3 majority blockhash")
   397  	}
   398  	if !voteSet.HasTwoThirdsAny() {
   399  		t.Errorf("we should have 2/3 if any votes")
   400  	}
   401  }
   402  
   403  func TestVoteSet_MakeCommit(t *testing.T) {
   404  	height, round := int64(1), int32(0)
   405  	voteSet, _, privValidators := randVoteSet(height, round, tmproto.PrecommitType, 10, 1)
   406  	blockHash, blockPartSetHeader := crypto.CRandBytes(32), PartSetHeader{123, crypto.CRandBytes(32)}
   407  
   408  	voteProto := &Vote{
   409  		ValidatorAddress: nil,
   410  		ValidatorIndex:   -1,
   411  		Height:           height,
   412  		Round:            round,
   413  		Timestamp:        tmtime.Now(),
   414  		Type:             tmproto.PrecommitType,
   415  		BlockID:          BlockID{blockHash, blockPartSetHeader},
   416  	}
   417  
   418  	// 6 out of 10 voted for some block.
   419  	for i := int32(0); i < 6; i++ {
   420  		pv, err := privValidators[i].GetPubKey(context.Background())
   421  		assert.NoError(t, err)
   422  		addr := pv.Address()
   423  		vote := withValidator(voteProto, addr, i)
   424  		_, err = signAddVote(privValidators[i], vote, voteSet)
   425  		if err != nil {
   426  			t.Error(err)
   427  		}
   428  	}
   429  
   430  	// MakeCommit should fail.
   431  	assert.Panics(t, func() { voteSet.MakeCommit() }, "Doesn't have +2/3 majority")
   432  
   433  	// 7th voted for some other block.
   434  	{
   435  		pv, err := privValidators[6].GetPubKey(context.Background())
   436  		assert.NoError(t, err)
   437  		addr := pv.Address()
   438  		vote := withValidator(voteProto, addr, 6)
   439  		vote = withBlockHash(vote, tmrand.Bytes(32))
   440  		vote = withBlockPartSetHeader(vote, PartSetHeader{123, tmrand.Bytes(32)})
   441  
   442  		_, err = signAddVote(privValidators[6], vote, voteSet)
   443  		require.NoError(t, err)
   444  	}
   445  
   446  	// The 8th voted like everyone else.
   447  	{
   448  		pv, err := privValidators[7].GetPubKey(context.Background())
   449  		assert.NoError(t, err)
   450  		addr := pv.Address()
   451  		vote := withValidator(voteProto, addr, 7)
   452  		_, err = signAddVote(privValidators[7], vote, voteSet)
   453  		require.NoError(t, err)
   454  	}
   455  
   456  	// The 9th voted for nil.
   457  	{
   458  		pv, err := privValidators[8].GetPubKey(context.Background())
   459  		assert.NoError(t, err)
   460  		addr := pv.Address()
   461  		vote := withValidator(voteProto, addr, 8)
   462  		vote.BlockID = BlockID{}
   463  
   464  		_, err = signAddVote(privValidators[8], vote, voteSet)
   465  		require.NoError(t, err)
   466  	}
   467  
   468  	commit := voteSet.MakeCommit()
   469  
   470  	// Commit should have 10 elements
   471  	assert.Equal(t, 10, len(commit.Signatures))
   472  
   473  	// Ensure that Commit is good.
   474  	if err := commit.ValidateBasic(); err != nil {
   475  		t.Errorf("error in Commit.ValidateBasic(): %v", err)
   476  	}
   477  }
   478  
   479  // NOTE: privValidators are in order
   480  func randVoteSet(
   481  	height int64,
   482  	round int32,
   483  	signedMsgType tmproto.SignedMsgType,
   484  	numValidators int,
   485  	votingPower int64,
   486  ) (*VoteSet, *ValidatorSet, []PrivValidator) {
   487  	valSet, privValidators := randValidatorPrivValSet(numValidators, votingPower)
   488  	return NewVoteSet("test_chain_id", height, round, signedMsgType, valSet), valSet, privValidators
   489  }
   490  
   491  func deterministicVoteSet(
   492  	height int64,
   493  	round int32,
   494  	signedMsgType tmproto.SignedMsgType,
   495  	votingPower int64,
   496  ) (*VoteSet, *ValidatorSet, []PrivValidator) {
   497  	valSet, privValidators := deterministicValidatorSet()
   498  	return NewVoteSet("test_chain_id", height, round, signedMsgType, valSet), valSet, privValidators
   499  }
   500  
   501  func randValidatorPrivValSet(numValidators int, votingPower int64) (*ValidatorSet, []PrivValidator) {
   502  	var (
   503  		valz           = make([]*Validator, numValidators)
   504  		privValidators = make([]PrivValidator, numValidators)
   505  	)
   506  
   507  	for i := 0; i < numValidators; i++ {
   508  		val, privValidator := randValidator(false, votingPower)
   509  		valz[i] = val
   510  		privValidators[i] = privValidator
   511  	}
   512  
   513  	sort.Sort(PrivValidatorsByAddress(privValidators))
   514  
   515  	return NewValidatorSet(valz), privValidators
   516  }
   517  
   518  // Convenience: Return new vote with different validator address/index
   519  func withValidator(vote *Vote, addr []byte, idx int32) *Vote {
   520  	vote = vote.Copy()
   521  	vote.ValidatorAddress = addr
   522  	vote.ValidatorIndex = idx
   523  	return vote
   524  }
   525  
   526  // Convenience: Return new vote with different height
   527  func withHeight(vote *Vote, height int64) *Vote {
   528  	vote = vote.Copy()
   529  	vote.Height = height
   530  	return vote
   531  }
   532  
   533  // Convenience: Return new vote with different round
   534  func withRound(vote *Vote, round int32) *Vote {
   535  	vote = vote.Copy()
   536  	vote.Round = round
   537  	return vote
   538  }
   539  
   540  // Convenience: Return new vote with different type
   541  func withType(vote *Vote, signedMsgType byte) *Vote {
   542  	vote = vote.Copy()
   543  	vote.Type = tmproto.SignedMsgType(signedMsgType)
   544  	return vote
   545  }
   546  
   547  // Convenience: Return new vote with different blockHash
   548  func withBlockHash(vote *Vote, blockHash []byte) *Vote {
   549  	vote = vote.Copy()
   550  	vote.BlockID.Hash = blockHash
   551  	return vote
   552  }
   553  
   554  // Convenience: Return new vote with different blockParts
   555  func withBlockPartSetHeader(vote *Vote, blockPartsHeader PartSetHeader) *Vote {
   556  	vote = vote.Copy()
   557  	vote.BlockID.PartSetHeader = blockPartsHeader
   558  	return vote
   559  }