package consensus import ( "encoding/hex" gio "io" "math/rand" "testing" "time" "github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/require" ) var messageTypes = []messageType{ changeViewType, prepareRequestType, prepareResponseType, commitType, recoveryRequestType, recoveryMessageType, } func TestConsensusPayload_Hash(t *testing.T) { dataHex := "00000000d8fb8d3b143b5f98468ef701909c976505a110a01e26c5e99be9a90cff979199b6fc33000400000000008d2000184dc95de24018f9ad71f4448a2b438eaca8b4b2ab6b4524b5a69a45d920c35103f3901444320656c390ff39c0062f5e8e138ce446a40c7e4ba1af1f8247ebbdf49295933715d3a67949714ff924f8a28cec5b954c71eca3bfaf0e9d4b1f87b4e21e9ba4ae18f97de71501b5c5d07edc200bd66a46b9b28b1a371f2195c10b0af90000e24018f900000000014140c9faaee59942f58da0e5268bc199632f2a3ad0fcbee68681a4437f140b49512e8d9efc6880eb44d3490782895a5794f35eeccee2923ce0c76fa7a1890f934eac232103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1ac" data, err := hex.DecodeString(dataHex) require.NoError(t, err) r := io.NewBinReaderFromBuf(data) var p Payload p.DecodeBinary(r) require.NoError(t, err) require.Equal(t, p.Hash().String(), "45859759c8491597804f1922773947e0d37bf54484af82f80cd642f7b063aa56") } func TestConsensusPayload_Serializable(t *testing.T) { for _, mt := range messageTypes { p := randomPayload(t, mt) testSerializable(t, p, new(Payload)) } } func TestCommit_Serializable(t *testing.T) { c := randomMessage(t, commitType) testSerializable(t, c, new(commit)) } func TestPrepareResponse_Serializable(t *testing.T) { resp := randomMessage(t, prepareResponseType) testSerializable(t, resp, new(prepareResponse)) } func TestPrepareRequest_Serializable(t *testing.T) { req := randomMessage(t, prepareRequestType) testSerializable(t, req, new(prepareRequest)) } func TestRecoveryRequest_Serializable(t *testing.T) { req := randomMessage(t, recoveryRequestType) testSerializable(t, req, new(recoveryRequest)) } func TestRecoveryMessage_Serializable(t *testing.T) { msg := randomMessage(t, recoveryMessageType) testSerializable(t, msg, new(recoveryMessage)) } func randomPayload(t *testing.T, mt messageType) *Payload { p := &Payload{ message: message{ Type: mt, ViewNumber: byte(rand.Uint32()), payload: randomMessage(t, mt), }, Version: 1, ValidatorIndex: 13, Height: rand.Uint32(), Timestamp: rand.Uint32(), Witness: transaction.Witness{ InvocationScript: fillRandom(t, make([]byte, 3)), VerificationScript: fillRandom(t, make([]byte, 4)), }, } fillRandom(t, p.PrevHash[:]) if mt == changeViewType { p.payload.(*changeView).NewViewNumber = p.ViewNumber + 1 } return p } func randomMessage(t *testing.T, mt messageType) io.Serializable { switch mt { case changeViewType: return &changeView{ Timestamp: rand.Uint32(), } case prepareRequestType: return randomPrepareRequest(t) case prepareResponseType: resp := &prepareResponse{} fillRandom(t, resp.PreparationHash[:]) return resp case commitType: var c commit fillRandom(t, c.Signature[:]) return &c case recoveryRequestType: return &recoveryRequest{Timestamp: rand.Uint32()} case recoveryMessageType: return randomRecoveryMessage(t) default: require.Fail(t, "invalid type") return nil } } func randomPrepareRequest(t *testing.T) *prepareRequest { const txCount = 3 req := &prepareRequest{ Timestamp: rand.Uint32(), Nonce: rand.Uint64(), TransactionHashes: make([]util.Uint256, txCount), MinerTransaction: *newMinerTx(rand.Uint32()), } req.TransactionHashes[0] = req.MinerTransaction.Hash() for i := 1; i < txCount; i++ { fillRandom(t, req.TransactionHashes[i][:]) } fillRandom(t, req.NextConsensus[:]) return req } func randomRecoveryMessage(t *testing.T) *recoveryMessage { result := randomMessage(t, prepareRequestType) require.IsType(t, (*prepareRequest)(nil), result) prepReq := result.(*prepareRequest) return &recoveryMessage{ PreparationPayloads: []*preparationCompact{ { ValidatorIndex: 1, InvocationScript: fillRandom(t, make([]byte, 10)), }, }, CommitPayloads: []*commitCompact{ { ViewNumber: 0, ValidatorIndex: 1, Signature: fillRandom(t, make([]byte, signatureSize)), InvocationScript: fillRandom(t, make([]byte, 20)), }, { ViewNumber: 0, ValidatorIndex: 2, Signature: fillRandom(t, make([]byte, signatureSize)), InvocationScript: fillRandom(t, make([]byte, 10)), }, }, ChangeViewPayloads: []*changeViewCompact{ { Timestamp: rand.Uint32(), ValidatorIndex: 3, OriginalViewNumber: 3, InvocationScript: fillRandom(t, make([]byte, 4)), }, }, PrepareRequest: prepReq, } } func TestMessageType_String(t *testing.T) { require.Equal(t, "ChangeView", changeViewType.String()) require.Equal(t, "PrepareRequest", prepareRequestType.String()) require.Equal(t, "PrepareResponse", prepareResponseType.String()) require.Equal(t, "Commit", commitType.String()) require.Equal(t, "RecoveryMessage", recoveryMessageType.String()) require.Equal(t, "RecoveryRequest", recoveryRequestType.String()) require.Equal(t, "UNKNOWN(0xff)", messageType(0xff).String()) } func testSerializable(t *testing.T, expected, actual io.Serializable) { w := io.NewBufBinWriter() expected.EncodeBinary(w.BinWriter) r := io.NewBinReaderFromBuf(w.Bytes()) actual.DecodeBinary(r) require.Equal(t, expected, actual) } func fillRandom(t *testing.T, buf []byte) []byte { r := rand.New(rand.NewSource(time.Now().Unix())) _, err := gio.ReadFull(r, buf) require.NoError(t, err) return buf } func newMinerTx(nonce uint32) *transaction.Transaction { return &transaction.Transaction{ Type: transaction.MinerType, Version: 0, Data: &transaction.MinerTX{ Nonce: rand.Uint32(), }, Attributes: []*transaction.Attribute{}, Inputs: []*transaction.Input{}, Outputs: []*transaction.Output{}, Scripts: []*transaction.Witness{}, Trimmed: false, } }