From 4d9554ab1633705a21adbe861ffee9a2c68f1111 Mon Sep 17 00:00:00 2001 From: Kacper Sawicki Date: Tue, 19 Nov 2024 17:01:12 +0000 Subject: [PATCH] v2alpha1: support ineffectual transaction state (#6468) ## Motivation #6443 Introduced new transaction state `TRANSACTION_STATE_INEFFECTUAL` which was not supported by v2alpha1. Closes #6466 --- api/grpcserver/v2alpha1/transaction.go | 34 ++++++++++++---- api/grpcserver/v2alpha1/transaction_mocks.go | 42 ++++++++++++++++++++ api/grpcserver/v2alpha1/transaction_test.go | 34 ++++++++++++++++ go.mod | 2 +- go.sum | 4 +- 5 files changed, 106 insertions(+), 10 deletions(-) diff --git a/api/grpcserver/v2alpha1/transaction.go b/api/grpcserver/v2alpha1/transaction.go index 9804006286..5876cdc419 100644 --- a/api/grpcserver/v2alpha1/transaction.go +++ b/api/grpcserver/v2alpha1/transaction.go @@ -39,6 +39,7 @@ const ( // transactionConState is an API to validate transaction. type transactionConState interface { Validation(raw types.RawTx) system.ValidationRequest + HasEvicted(tid types.TransactionID) (bool, error) } // transactionSyncer is an API to get sync status. @@ -130,7 +131,7 @@ func (s *TransactionService) List( if err := transactions.IterateTransactionsOps(s.db, ops, func(tx *types.MeshTransaction, result *types.TransactionResult, ) bool { - rst = append(rst, toTx(tx, result, request.IncludeResult, request.IncludeState)) + rst = append(rst, s.toTx(ctx, tx, result, request.IncludeResult, request.IncludeState)) return true }); err != nil { return nil, status.Error(codes.Internal, err.Error()) @@ -303,7 +304,10 @@ func toTransactionOperations(filter *spacemeshv2alpha1.TransactionRequest) (buil return ops, nil } -func toTx(tx *types.MeshTransaction, result *types.TransactionResult, +func (s *TransactionService) toTx( + ctx context.Context, + tx *types.MeshTransaction, + result *types.TransactionResult, includeResult, includeState bool, ) *spacemeshv2alpha1.TransactionResponse { rst := &spacemeshv2alpha1.TransactionResponse{} @@ -332,7 +336,7 @@ func toTx(tx *types.MeshTransaction, result *types.TransactionResult, if includeResult && result != nil { rst.TxResult = &spacemeshv2alpha1.TransactionResult{ - Status: convertTxResult(result), + Status: s.convertTxResult(result), Message: result.Message, GasConsumed: result.Gas, Fee: result.Fee, @@ -348,7 +352,7 @@ func toTx(tx *types.MeshTransaction, result *types.TransactionResult, } if includeState { - rst.TxState = convertTxState(tx) + rst.TxState = s.convertTxState(ctx, tx) } rst.Tx = t @@ -356,7 +360,9 @@ func toTx(tx *types.MeshTransaction, result *types.TransactionResult, return rst } -func convertTxResult(result *types.TransactionResult) spacemeshv2alpha1.TransactionResult_Status { +func (s *TransactionService) convertTxResult( + result *types.TransactionResult, +) spacemeshv2alpha1.TransactionResult_Status { switch result.Status { case types.TransactionSuccess: return spacemeshv2alpha1.TransactionResult_TRANSACTION_STATUS_SUCCESS @@ -367,8 +373,9 @@ func convertTxResult(result *types.TransactionResult) spacemeshv2alpha1.Transact } } -// TODO: REJECTED, INSUFFICIENT_FUNDS, CONFLICTING, MESH. -func convertTxState(tx *types.MeshTransaction) *spacemeshv2alpha1.TransactionState { +func (s *TransactionService) convertTxState( + ctx context.Context, tx *types.MeshTransaction, +) *spacemeshv2alpha1.TransactionState { switch tx.State { case types.MEMPOOL: state := spacemeshv2alpha1.TransactionState_TRANSACTION_STATE_MEMPOOL @@ -377,6 +384,19 @@ func convertTxState(tx *types.MeshTransaction) *spacemeshv2alpha1.TransactionSta state := spacemeshv2alpha1.TransactionState_TRANSACTION_STATE_PROCESSED return &state default: + evicted, err := s.conState.HasEvicted(tx.ID) + if err != nil { + ctxzap.Debug(ctx, "failed to check if tx is evicted", + zap.String("tx_id", tx.ID.String()), + zap.Error(err), + ) + state := spacemeshv2alpha1.TransactionState_TRANSACTION_STATE_UNSPECIFIED + return &state + } + if evicted { + state := spacemeshv2alpha1.TransactionState_TRANSACTION_STATE_INEFFECTUAL + return &state + } state := spacemeshv2alpha1.TransactionState_TRANSACTION_STATE_UNSPECIFIED return &state } diff --git a/api/grpcserver/v2alpha1/transaction_mocks.go b/api/grpcserver/v2alpha1/transaction_mocks.go index e908a24179..e6efa1d8ff 100644 --- a/api/grpcserver/v2alpha1/transaction_mocks.go +++ b/api/grpcserver/v2alpha1/transaction_mocks.go @@ -22,6 +22,7 @@ import ( type MocktransactionConState struct { ctrl *gomock.Controller recorder *MocktransactionConStateMockRecorder + isgomock struct{} } // MocktransactionConStateMockRecorder is the mock recorder for MocktransactionConState. @@ -41,6 +42,45 @@ func (m *MocktransactionConState) EXPECT() *MocktransactionConStateMockRecorder return m.recorder } +// HasEvicted mocks base method. +func (m *MocktransactionConState) HasEvicted(tid types.TransactionID) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasEvicted", tid) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HasEvicted indicates an expected call of HasEvicted. +func (mr *MocktransactionConStateMockRecorder) HasEvicted(tid any) *MocktransactionConStateHasEvictedCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasEvicted", reflect.TypeOf((*MocktransactionConState)(nil).HasEvicted), tid) + return &MocktransactionConStateHasEvictedCall{Call: call} +} + +// MocktransactionConStateHasEvictedCall wrap *gomock.Call +type MocktransactionConStateHasEvictedCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MocktransactionConStateHasEvictedCall) Return(arg0 bool, arg1 error) *MocktransactionConStateHasEvictedCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MocktransactionConStateHasEvictedCall) Do(f func(types.TransactionID) (bool, error)) *MocktransactionConStateHasEvictedCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MocktransactionConStateHasEvictedCall) DoAndReturn(f func(types.TransactionID) (bool, error)) *MocktransactionConStateHasEvictedCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // Validation mocks base method. func (m *MocktransactionConState) Validation(raw types.RawTx) system.ValidationRequest { m.ctrl.T.Helper() @@ -83,6 +123,7 @@ func (c *MocktransactionConStateValidationCall) DoAndReturn(f func(types.RawTx) type MocktransactionSyncer struct { ctrl *gomock.Controller recorder *MocktransactionSyncerMockRecorder + isgomock struct{} } // MocktransactionSyncerMockRecorder is the mock recorder for MocktransactionSyncer. @@ -144,6 +185,7 @@ func (c *MocktransactionSyncerIsSyncedCall) DoAndReturn(f func(context.Context) type MocktransactionValidator struct { ctrl *gomock.Controller recorder *MocktransactionValidatorMockRecorder + isgomock struct{} } // MocktransactionValidatorMockRecorder is the mock recorder for MocktransactionValidator. diff --git a/api/grpcserver/v2alpha1/transaction_test.go b/api/grpcserver/v2alpha1/transaction_test.go index 6177c7e9b2..1d2af9320b 100644 --- a/api/grpcserver/v2alpha1/transaction_test.go +++ b/api/grpcserver/v2alpha1/transaction_test.go @@ -757,3 +757,37 @@ func TestToTxContents(t *testing.T) { require.Equal(t, spacemeshv2alpha1.Transaction_TRANSACTION_TYPE_VESTING_SPAWN, txType) }) } + +func TestEvictedTransaction(t *testing.T) { + req := require.New(t) + + db := statesql.InMemoryTest(t) + ctrl := gomock.NewController(t) + syncer := NewMocktransactionSyncer(ctrl) + publisher := pubsubmocks.NewMockPublisher(ctrl) + txHandler := NewMocktransactionValidator(ctrl) + conState := NewMocktransactionConState(ctrl) + + signer, err := signing.NewEdSigner() + require.NoError(t, err) + tx := newTx(0, types.Address{}, signer) + require.NoError(t, transactions.Add(db, &types.Transaction{ + RawTx: tx.RawTx, + TxHeader: nil, + }, time.Now())) + + svc := NewTransactionService(db, conState, syncer, txHandler, publisher) + cfg, cleanup := launchServer(t, svc) + t.Cleanup(cleanup) + + conn := dialGrpc(t, cfg) + client := spacemeshv2alpha1.NewTransactionServiceClient(conn) + + conState.EXPECT().HasEvicted(gomock.Any()).Return(true, nil) + list, err := client.List(context.Background(), &spacemeshv2alpha1.TransactionRequest{Limit: 1, IncludeState: true}) + req.NoError(err) + req.Len(list.Transactions, 1) + req.Equal(tx.ID.Bytes(), list.Transactions[0].Tx.Id) + req.Equal(list.Transactions[0].TxState.String(), + spacemeshv2alpha1.TransactionState_TRANSACTION_STATE_INEFFECTUAL.String()) +} diff --git a/go.mod b/go.mod index 13fff3a813..26374ddfc5 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/seehuhn/mt19937 v1.0.0 github.com/slok/go-http-metrics v0.13.0 - github.com/spacemeshos/api/release/go v1.56.0 + github.com/spacemeshos/api/release/go v1.57.0 github.com/spacemeshos/economics v0.1.4 github.com/spacemeshos/fixed v0.1.2 github.com/spacemeshos/go-scale v1.2.1 diff --git a/go.sum b/go.sum index 8f1925e66f..c0f4db5a0a 100644 --- a/go.sum +++ b/go.sum @@ -629,8 +629,8 @@ github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:Udh github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= -github.com/spacemeshos/api/release/go v1.56.0 h1:llBVijoO4I3mhHk0OtGJdTT/11I7ajo0CZp3x8h1EjA= -github.com/spacemeshos/api/release/go v1.56.0/go.mod h1:6o17nhNyXpbVeijAQqkZfL8Pe/IkMGAWMLSLZni0DOU= +github.com/spacemeshos/api/release/go v1.57.0 h1:KE4zNmuI12q7MDcy2cDwQLSVWKpY3gXoDgX02URFzBk= +github.com/spacemeshos/api/release/go v1.57.0/go.mod h1:w/WRxR8JVmaeKqEEyL6DCoQxt3oyZOTv4uQSdMXlucM= github.com/spacemeshos/economics v0.1.4 h1:twlawrcQhYNqPgyDv08+24EL/OgUKz3d7q+PvJIAND0= github.com/spacemeshos/economics v0.1.4/go.mod h1:6HKWKiKdxjVQcGa2z/wA0LR4M/DzKib856bP16yqNmQ= github.com/spacemeshos/fixed v0.1.2 h1:pENQ8pXFAqin3f15ZLoOVVeSgcmcFJ0IFdFm4+9u4SM=