github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/consensus/payload_test.go (about)

     1  package consensus
     2  
     3  import (
     4  	"encoding/hex"
     5  	gio "io"
     6  	"math/rand"
     7  	"testing"
     8  
     9  	"github.com/nspcc-dev/dbft"
    10  	"github.com/nspcc-dev/neo-go/internal/random"
    11  	"github.com/nspcc-dev/neo-go/internal/testserdes"
    12  	"github.com/nspcc-dev/neo-go/pkg/config/netmode"
    13  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    14  	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
    15  	"github.com/nspcc-dev/neo-go/pkg/io"
    16  	npayload "github.com/nspcc-dev/neo-go/pkg/network/payload"
    17  	"github.com/nspcc-dev/neo-go/pkg/util"
    18  	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  var messageTypes = []messageType{
    24  	changeViewType,
    25  	prepareRequestType,
    26  	prepareResponseType,
    27  	commitType,
    28  	recoveryRequestType,
    29  	recoveryMessageType,
    30  }
    31  
    32  func TestConsensusPayload_Getters(t *testing.T) {
    33  	var p = &Payload{
    34  		Extensible: npayload.Extensible{},
    35  		message: message{
    36  			Type:           prepareRequestType,
    37  			BlockIndex:     11,
    38  			ValidatorIndex: 4,
    39  			ViewNumber:     2,
    40  		},
    41  	}
    42  
    43  	assert.EqualValues(t, 4, p.ValidatorIndex())
    44  	assert.EqualValues(t, 11, p.Height())
    45  	assert.EqualValues(t, 2, p.ViewNumber())
    46  	assert.Equal(t, dbft.PrepareRequestType, p.Type())
    47  
    48  	pl := randomMessage(t, prepareRequestType)
    49  	p.payload = pl
    50  	require.Equal(t, pl, p.Payload())
    51  	require.Equal(t, pl, p.GetPrepareRequest())
    52  
    53  	pl = randomMessage(t, prepareResponseType)
    54  	p.payload = pl
    55  	require.Equal(t, pl, p.GetPrepareResponse())
    56  
    57  	pl = randomMessage(t, commitType)
    58  	p.payload = pl
    59  	require.Equal(t, pl, p.GetCommit())
    60  
    61  	pl = randomMessage(t, changeViewType)
    62  	p.payload = pl
    63  	require.Equal(t, pl, p.GetChangeView())
    64  
    65  	pl = randomMessage(t, recoveryRequestType)
    66  	p.payload = pl
    67  	require.Equal(t, pl, p.GetRecoveryRequest())
    68  
    69  	pl = randomMessage(t, recoveryMessageType)
    70  	p.payload = pl
    71  	require.Equal(t, pl, p.GetRecoveryMessage())
    72  }
    73  
    74  func TestConsensusPayload_Serializable(t *testing.T) {
    75  	for _, mt := range messageTypes {
    76  		p := randomPayload(t, mt)
    77  		actual := &Payload{Extensible: npayload.Extensible{}, network: netmode.UnitTestNet}
    78  		data, err := testserdes.EncodeBinary(p)
    79  		require.NoError(t, err)
    80  		require.NoError(t, testserdes.DecodeBinary(data, &actual.Extensible))
    81  		assert.NoError(t, actual.decodeData())
    82  		require.Equal(t, p, actual)
    83  	}
    84  }
    85  
    86  func TestConsensusPayload_DecodeBinaryInvalid(t *testing.T) {
    87  	// PrepareResponse payload consists of:
    88  	// - 1-byte message type (PrepareResponse)
    89  	// - 4-byte block index
    90  	// - 1-byte validator index
    91  	// - 1-byte view number
    92  	// - 32-byte preparation hash
    93  	const (
    94  		typeIndex = 0
    95  		size      = 39
    96  	)
    97  
    98  	buf := make([]byte, size)
    99  	expected := message{
   100  		Type:    prepareResponseType,
   101  		payload: &prepareResponse{},
   102  	}
   103  
   104  	// valid payload
   105  	buf[typeIndex] = byte(prepareResponseType)
   106  	p := &Payload{Extensible: npayload.Extensible{Data: buf}}
   107  	require.NoError(t, p.decodeData())
   108  	require.Equal(t, expected, p.message)
   109  
   110  	// invalid type
   111  	buf[typeIndex] = 0xFF
   112  	p = &Payload{Extensible: npayload.Extensible{Data: buf}}
   113  	require.Error(t, p.decodeData())
   114  
   115  	// invalid length
   116  	buf[typeIndex] = byte(prepareResponseType)
   117  	p = &Payload{Extensible: npayload.Extensible{Data: buf[:len(buf)-1]}}
   118  	require.Error(t, p.decodeData())
   119  }
   120  
   121  func TestCommit_Serializable(t *testing.T) {
   122  	c := randomMessage(t, commitType)
   123  	testserdes.EncodeDecodeBinary(t, c, new(commit))
   124  }
   125  
   126  func TestPrepareResponse_Serializable(t *testing.T) {
   127  	resp := randomMessage(t, prepareResponseType)
   128  	testserdes.EncodeDecodeBinary(t, resp, new(prepareResponse))
   129  }
   130  
   131  func TestPrepareRequest_Serializable(t *testing.T) {
   132  	req := randomMessage(t, prepareRequestType)
   133  	testserdes.EncodeDecodeBinary(t, req, new(prepareRequest))
   134  }
   135  
   136  func TestRecoveryRequest_Serializable(t *testing.T) {
   137  	req := randomMessage(t, recoveryRequestType)
   138  	testserdes.EncodeDecodeBinary(t, req, new(recoveryRequest))
   139  }
   140  
   141  func TestRecoveryMessage_Serializable(t *testing.T) {
   142  	msg := randomMessage(t, recoveryMessageType)
   143  	testserdes.EncodeDecodeBinary(t, msg, new(recoveryMessage))
   144  }
   145  
   146  func randomPayload(t *testing.T, mt messageType) *Payload {
   147  	p := &Payload{
   148  		message: message{
   149  			Type:           mt,
   150  			ValidatorIndex: byte(rand.Uint32()),
   151  			BlockIndex:     rand.Uint32(),
   152  			ViewNumber:     byte(rand.Uint32()),
   153  			payload:        randomMessage(t, mt),
   154  		},
   155  		Extensible: npayload.Extensible{
   156  			Witness: transaction.Witness{
   157  				InvocationScript:   random.Bytes(3),
   158  				VerificationScript: []byte{byte(opcode.PUSH0)},
   159  			},
   160  		},
   161  		network: netmode.UnitTestNet,
   162  	}
   163  
   164  	if mt == changeViewType {
   165  		p.payload.(*changeView).newViewNumber = p.ViewNumber() + 1
   166  	}
   167  
   168  	return p
   169  }
   170  
   171  func randomMessage(t *testing.T, mt messageType) io.Serializable {
   172  	switch mt {
   173  	case changeViewType:
   174  		return &changeView{
   175  			timestamp: rand.Uint64(),
   176  		}
   177  	case prepareRequestType:
   178  		return randomPrepareRequest(t)
   179  	case prepareResponseType:
   180  		return &prepareResponse{preparationHash: random.Uint256()}
   181  	case commitType:
   182  		var c commit
   183  		random.Fill(c.signature[:])
   184  		return &c
   185  	case recoveryRequestType:
   186  		return &recoveryRequest{timestamp: rand.Uint64()}
   187  	case recoveryMessageType:
   188  		return randomRecoveryMessage(t)
   189  	default:
   190  		require.Fail(t, "invalid type")
   191  		return nil
   192  	}
   193  }
   194  
   195  func randomPrepareRequest(t *testing.T) *prepareRequest {
   196  	const txCount = 3
   197  
   198  	req := &prepareRequest{
   199  		timestamp:         rand.Uint64(),
   200  		transactionHashes: make([]util.Uint256, txCount),
   201  	}
   202  
   203  	for i := 0; i < txCount; i++ {
   204  		req.transactionHashes[i] = random.Uint256()
   205  	}
   206  
   207  	return req
   208  }
   209  
   210  func randomRecoveryMessage(t *testing.T) *recoveryMessage {
   211  	result := randomMessage(t, prepareRequestType)
   212  	require.IsType(t, (*prepareRequest)(nil), result)
   213  	prepReq := result.(*prepareRequest)
   214  
   215  	return &recoveryMessage{
   216  		preparationPayloads: []*preparationCompact{
   217  			{
   218  				ValidatorIndex:   1,
   219  				InvocationScript: random.Bytes(10),
   220  			},
   221  		},
   222  		commitPayloads: []*commitCompact{
   223  			{
   224  				ViewNumber:       0,
   225  				ValidatorIndex:   1,
   226  				Signature:        [keys.SignatureLen]byte{1, 2, 3},
   227  				InvocationScript: random.Bytes(20),
   228  			},
   229  			{
   230  				ViewNumber:       0,
   231  				ValidatorIndex:   2,
   232  				Signature:        [keys.SignatureLen]byte{11, 3, 4, 98},
   233  				InvocationScript: random.Bytes(10),
   234  			},
   235  		},
   236  		changeViewPayloads: []*changeViewCompact{
   237  			{
   238  				Timestamp:          rand.Uint64(),
   239  				ValidatorIndex:     3,
   240  				OriginalViewNumber: 3,
   241  				InvocationScript:   random.Bytes(4),
   242  			},
   243  		},
   244  		prepareRequest: &message{
   245  			Type:    prepareRequestType,
   246  			payload: prepReq,
   247  		},
   248  	}
   249  }
   250  
   251  func TestPayload_Sign(t *testing.T) {
   252  	key, err := keys.NewPrivateKey()
   253  	require.NoError(t, err)
   254  
   255  	priv := &privateKey{key}
   256  
   257  	p := randomPayload(t, prepareRequestType)
   258  	h := priv.PublicKey().GetScriptHash()
   259  	bc := newTestChain(t, false)
   260  	_, err = bc.VerifyWitness(h, p, &p.Witness, payloadGasLimit)
   261  	require.Error(t, err)
   262  	require.NoError(t, p.Sign(priv))
   263  	_, err = bc.VerifyWitness(h, p, &p.Witness, payloadGasLimit)
   264  	require.NoError(t, err)
   265  }
   266  
   267  func TestMessageType_String(t *testing.T) {
   268  	require.Equal(t, "ChangeView", changeViewType.String())
   269  	require.Equal(t, "PrepareRequest", prepareRequestType.String())
   270  	require.Equal(t, "PrepareResponse", prepareResponseType.String())
   271  	require.Equal(t, "Commit", commitType.String())
   272  	require.Equal(t, "RecoveryMessage", recoveryMessageType.String())
   273  	require.Equal(t, "RecoveryRequest", recoveryRequestType.String())
   274  	require.Equal(t, "UNKNOWN(0xff)", messageType(0xff).String())
   275  }
   276  
   277  func TestPayload_DecodeFromPrivnet(t *testing.T) {
   278  	hexDump := "0464424654000000000200000018ca088345a4926bcc9a1daccfba0ac4436082a847300200000003003c57d952539c5e0c39a83c0de5744a772c0dcb0e8ccd7c5bba27ef" +
   279  		"498506cd860cdfd01ad215b251ab64dc64cd544a6f453f3b0128ddc98d95ac15915dbe6f6301420c40b39c9136af3b8186409dec2dbd31d0fd4f3e637b3eeb96d8556b41f8512dd25d91134f62a6c293db089b7e82b7a0fd23bf9a1" +
   280  		"5ee26c42a5738b913beef74176d290c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee6990b4195440d78"
   281  	data, err := hex.DecodeString(hexDump)
   282  	require.NoError(t, err)
   283  
   284  	buf := io.NewBinReaderFromBuf(data)
   285  	p := NewPayload(netmode.PrivNet, false)
   286  	p.DecodeBinary(buf)
   287  	require.NoError(t, buf.Err)
   288  	require.Equal(t, dbft.CommitType, p.Type())
   289  	require.Equal(t, uint32(2), p.Height())
   290  	require.Equal(t, uint16(3), p.ValidatorIndex())
   291  	require.Equal(t, byte(0), p.ViewNumber())
   292  	require.NotNil(t, p.message.payload)
   293  
   294  	buf.ReadB()
   295  	require.Equal(t, gio.EOF, buf.Err)
   296  }