From e1c0c6f794c60293f4f54227a360a07c107029d0 Mon Sep 17 00:00:00 2001 From: Nawi Kartini <33143058+nawikart@users.noreply.github.com> Date: Thu, 14 May 2020 08:25:49 +0800 Subject: [PATCH 1/6] Bug: Missing Rollback When BackupMempool Fail in PoppedOffBlock #802 (#816) * first commit * fix: Missing Rollback When BackupMempool Fail in PoppedOffBlock * change BackupMempols --> BackupMempools --- core/service/blockMainService.go | 65 +------------- core/service/blockMainService_test.go | 123 +++++++++++++++----------- core/service/mempoolCoreService.go | 78 ++++++++++++++++ 3 files changed, 154 insertions(+), 112 deletions(-) diff --git a/core/service/blockMainService.go b/core/service/blockMainService.go index fb5154757..87b6aa764 100644 --- a/core/service/blockMainService.go +++ b/core/service/blockMainService.go @@ -1279,10 +1279,8 @@ func (bs *BlockService) GetBlockExtendedInfo(block *model.Block, includeReceipts func (bs *BlockService) PopOffToBlock(commonBlock *model.Block) ([]*model.Block, error) { var ( - mempoolsBackupBytes *bytes.Buffer - mempoolsBackup []*model.MempoolTransaction - publishedReceipts []*model.PublishedReceipt - err error + publishedReceipts []*model.PublishedReceipt + err error ) // if current blockchain Height is lower than minimal height of the blockchain that is allowed to rollback lastBlock, err := bs.GetLastBlock() @@ -1328,72 +1326,17 @@ func (bs *BlockService) PopOffToBlock(commonBlock *model.Block) ([]*model.Block, } // Backup existing transactions from mempool before rollback - mempoolsBackup, err = bs.MempoolService.GetMempoolTransactionsWantToBackup(commonBlock.Height) + err = bs.MempoolService.BackupMempools(commonBlock) if err != nil { return nil, err } - bs.Logger.Warnf("mempool tx backup %d in total with block_height %d", len(mempoolsBackup), commonBlock.GetHeight()) - derivedQueries := query.GetDerivedQuery(bs.Chaintype) - err = bs.QueryExecutor.BeginTx() - if err != nil { - return nil, err - } - - mempoolsBackupBytes = bytes.NewBuffer([]byte{}) - - for _, mempool := range mempoolsBackup { - var ( - tx *model.Transaction - txType transaction.TypeAction - ) - tx, err := bs.TransactionUtil.ParseTransactionBytes(mempool.GetTransactionBytes(), true) - if err != nil { - return nil, err - } - txType, err = bs.ActionTypeSwitcher.GetTransactionType(tx) - if err != nil { - return nil, err - } - err = bs.TransactionCoreService.UndoApplyUnconfirmedTransaction(txType) - if err != nil { - return nil, err - } - - /* - mempoolsBackupBytes format is - [...{4}byteSize,{bytesSize}transactionBytes] - */ - sizeMempool := uint32(len(mempool.GetTransactionBytes())) - mempoolsBackupBytes.Write(commonUtils.ConvertUint32ToBytes(sizeMempool)) - mempoolsBackupBytes.Write(mempool.GetTransactionBytes()) - } - - for _, dQuery := range derivedQueries { - queries := dQuery.Rollback(commonBlock.Height) - err = bs.QueryExecutor.ExecuteTransactions(queries) - if err != nil { - _ = bs.QueryExecutor.RollbackTx() - return nil, err - } - } - err = bs.QueryExecutor.CommitTx() - if err != nil { - return nil, err - } - // // TODO: here we should also delete all snapshot files relative to the block manifests being rolled back during derived tables // rollback. Something like this: // - before rolling back derived queries, select all spine block manifest records from commonBlock.Height till last // - delete all snapshots referenced by them // - if mempoolsBackupBytes.Len() > 0 { - kvdbMempoolsBackupKey := commonUtils.GetKvDbMempoolDBKey(bs.GetChainType()) - err = bs.KVExecutor.Insert(kvdbMempoolsBackupKey, mempoolsBackupBytes.Bytes(), int(constant.KVDBMempoolsBackupExpiry)) - if err != nil { - return nil, err - } - } + // remove peer memoization bs.NodeRegistrationService.ResetScrambledNodes() // clear block pool diff --git a/core/service/blockMainService_test.go b/core/service/blockMainService_test.go index 6dd70bddf..df99437c3 100644 --- a/core/service/blockMainService_test.go +++ b/core/service/blockMainService_test.go @@ -4109,12 +4109,20 @@ func (*mockMempoolServiceBlockPopSuccess) GetMempoolTransactionsWantToBackup( return make([]*model.MempoolTransaction, 0), nil } +func (*mockMempoolServiceBlockPopSuccess) BackupMempools(commonBlock *model.Block) error { + return nil +} + func (*mockMempoolServiceBlockPopFail) GetMempoolTransactionsWantToBackup( height uint32, ) ([]*model.MempoolTransaction, error) { return nil, errors.New("mockedError") } +func (*mockMempoolServiceBlockPopFail) BackupMempools(commonBlock *model.Block) error { + return errors.New("error BackupMempools") +} + func (*mockReceiptSuccess) GetPublishedReceiptsByHeight(blockHeight uint32) ([]*model.PublishedReceipt, error) { return make([]*model.PublishedReceipt, 0), nil } @@ -4315,27 +4323,37 @@ func (*mockedExecutorPopOffToBlockSuccessPopping) ExecuteSelectRow(qStr string, func TestBlockService_PopOffToBlock(t *testing.T) { type fields struct { - Chaintype chaintype.ChainType - KVExecutor kvdb.KVExecutorInterface - QueryExecutor query.ExecutorInterface - BlockQuery query.BlockQueryInterface - MempoolQuery query.MempoolQueryInterface - TransactionQuery query.TransactionQueryInterface - MerkleTreeQuery query.MerkleTreeQueryInterface - PublishedReceiptQuery query.PublishedReceiptQueryInterface - SkippedBlocksmithQuery query.SkippedBlocksmithQueryInterface - Signature crypto.SignatureInterface - MempoolService MempoolServiceInterface - ReceiptService ReceiptServiceInterface - NodeRegistrationService NodeRegistrationServiceInterface - ActionTypeSwitcher transaction.TypeActionSwitcher - AccountBalanceQuery query.AccountBalanceQueryInterface - ParticipationScoreQuery query.ParticipationScoreQueryInterface - NodeRegistrationQuery query.NodeRegistrationQueryInterface - BlockPoolService BlockPoolServiceInterface - TransactionCoreService TransactionCoreServiceInterface - Observer *observer.Observer - Logger *log.Logger + RWMutex sync.RWMutex + Chaintype chaintype.ChainType + KVExecutor kvdb.KVExecutorInterface + QueryExecutor query.ExecutorInterface + BlockQuery query.BlockQueryInterface + MempoolQuery query.MempoolQueryInterface + TransactionQuery query.TransactionQueryInterface + PublishedReceiptQuery query.PublishedReceiptQueryInterface + SkippedBlocksmithQuery query.SkippedBlocksmithQueryInterface + Signature crypto.SignatureInterface + MempoolService MempoolServiceInterface + ReceiptService ReceiptServiceInterface + NodeRegistrationService NodeRegistrationServiceInterface + BlocksmithService BlocksmithServiceInterface + ActionTypeSwitcher transaction.TypeActionSwitcher + AccountBalanceQuery query.AccountBalanceQueryInterface + ParticipationScoreQuery query.ParticipationScoreQueryInterface + NodeRegistrationQuery query.NodeRegistrationQueryInterface + AccountLedgerQuery query.AccountLedgerQueryInterface + BlocksmithStrategy strategy.BlocksmithStrategyInterface + BlockIncompleteQueueService BlockIncompleteQueueServiceInterface + BlockPoolService BlockPoolServiceInterface + Observer *observer.Observer + Logger *log.Logger + TransactionUtil transaction.UtilInterface + ReceiptUtil coreUtil.ReceiptUtilInterface + PublishedReceiptUtil coreUtil.PublishedReceiptUtilInterface + TransactionCoreService TransactionCoreServiceInterface + CoinbaseService CoinbaseServiceInterface + ParticipationScoreService ParticipationScoreServiceInterface + PublishedReceiptService PublishedReceiptServiceInterface } type args struct { commonBlock *model.Block @@ -4356,7 +4374,6 @@ func TestBlockService_PopOffToBlock(t *testing.T) { BlockQuery: query.NewBlockQuery(&chaintype.MainChain{}), MempoolQuery: nil, TransactionQuery: query.NewTransactionQuery(&chaintype.MainChain{}), - MerkleTreeQuery: nil, PublishedReceiptQuery: nil, SkippedBlocksmithQuery: nil, Signature: nil, @@ -4386,7 +4403,6 @@ func TestBlockService_PopOffToBlock(t *testing.T) { BlockQuery: query.NewBlockQuery(&chaintype.MainChain{}), MempoolQuery: nil, TransactionQuery: query.NewTransactionQuery(&chaintype.MainChain{}), - MerkleTreeQuery: nil, PublishedReceiptQuery: nil, SkippedBlocksmithQuery: nil, Signature: nil, @@ -4417,7 +4433,6 @@ func TestBlockService_PopOffToBlock(t *testing.T) { BlockQuery: query.NewBlockQuery(&chaintype.MainChain{}), MempoolQuery: nil, TransactionQuery: query.NewTransactionQuery(&chaintype.MainChain{}), - MerkleTreeQuery: nil, PublishedReceiptQuery: nil, SkippedBlocksmithQuery: nil, Signature: nil, @@ -4448,7 +4463,6 @@ func TestBlockService_PopOffToBlock(t *testing.T) { BlockQuery: query.NewBlockQuery(&chaintype.MainChain{}), MempoolQuery: nil, TransactionQuery: query.NewTransactionQuery(&chaintype.MainChain{}), - MerkleTreeQuery: nil, PublishedReceiptQuery: nil, SkippedBlocksmithQuery: nil, Signature: nil, @@ -4479,7 +4493,6 @@ func TestBlockService_PopOffToBlock(t *testing.T) { BlockQuery: query.NewBlockQuery(&chaintype.MainChain{}), MempoolQuery: nil, TransactionQuery: query.NewTransactionQuery(&chaintype.MainChain{}), - MerkleTreeQuery: nil, PublishedReceiptQuery: nil, SkippedBlocksmithQuery: nil, Signature: nil, @@ -4510,7 +4523,6 @@ func TestBlockService_PopOffToBlock(t *testing.T) { BlockQuery: query.NewBlockQuery(&chaintype.MainChain{}), MempoolQuery: nil, TransactionQuery: query.NewTransactionQuery(&chaintype.MainChain{}), - MerkleTreeQuery: nil, PublishedReceiptQuery: nil, SkippedBlocksmithQuery: nil, Signature: nil, @@ -4541,7 +4553,6 @@ func TestBlockService_PopOffToBlock(t *testing.T) { BlockQuery: query.NewBlockQuery(&chaintype.MainChain{}), MempoolQuery: nil, TransactionQuery: query.NewTransactionQuery(&chaintype.MainChain{}), - MerkleTreeQuery: nil, PublishedReceiptQuery: nil, SkippedBlocksmithQuery: nil, Signature: nil, @@ -4584,38 +4595,48 @@ func TestBlockService_PopOffToBlock(t *testing.T) { wantErr: false, }, } - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { bs := &BlockService{ - Chaintype: tt.fields.Chaintype, - KVExecutor: tt.fields.KVExecutor, - QueryExecutor: tt.fields.QueryExecutor, - BlockQuery: tt.fields.BlockQuery, - MempoolQuery: tt.fields.MempoolQuery, - TransactionQuery: tt.fields.TransactionQuery, - PublishedReceiptQuery: tt.fields.PublishedReceiptQuery, - SkippedBlocksmithQuery: tt.fields.SkippedBlocksmithQuery, - Signature: tt.fields.Signature, - MempoolService: tt.fields.MempoolService, - ReceiptService: tt.fields.ReceiptService, - NodeRegistrationService: tt.fields.NodeRegistrationService, - ActionTypeSwitcher: tt.fields.ActionTypeSwitcher, - AccountBalanceQuery: tt.fields.AccountBalanceQuery, - ParticipationScoreQuery: tt.fields.ParticipationScoreQuery, - NodeRegistrationQuery: tt.fields.NodeRegistrationQuery, - BlockPoolService: tt.fields.BlockPoolService, - TransactionCoreService: tt.fields.TransactionCoreService, - Observer: tt.fields.Observer, - Logger: tt.fields.Logger, + RWMutex: tt.fields.RWMutex, + Chaintype: tt.fields.Chaintype, + KVExecutor: tt.fields.KVExecutor, + QueryExecutor: tt.fields.QueryExecutor, + BlockQuery: tt.fields.BlockQuery, + MempoolQuery: tt.fields.MempoolQuery, + TransactionQuery: tt.fields.TransactionQuery, + PublishedReceiptQuery: tt.fields.PublishedReceiptQuery, + SkippedBlocksmithQuery: tt.fields.SkippedBlocksmithQuery, + Signature: tt.fields.Signature, + MempoolService: tt.fields.MempoolService, + ReceiptService: tt.fields.ReceiptService, + NodeRegistrationService: tt.fields.NodeRegistrationService, + BlocksmithService: tt.fields.BlocksmithService, + ActionTypeSwitcher: tt.fields.ActionTypeSwitcher, + AccountBalanceQuery: tt.fields.AccountBalanceQuery, + ParticipationScoreQuery: tt.fields.ParticipationScoreQuery, + NodeRegistrationQuery: tt.fields.NodeRegistrationQuery, + AccountLedgerQuery: tt.fields.AccountLedgerQuery, + BlocksmithStrategy: tt.fields.BlocksmithStrategy, + BlockIncompleteQueueService: tt.fields.BlockIncompleteQueueService, + BlockPoolService: tt.fields.BlockPoolService, + Observer: tt.fields.Observer, + Logger: tt.fields.Logger, + TransactionUtil: tt.fields.TransactionUtil, + ReceiptUtil: tt.fields.ReceiptUtil, + PublishedReceiptUtil: tt.fields.PublishedReceiptUtil, + TransactionCoreService: tt.fields.TransactionCoreService, + CoinbaseService: tt.fields.CoinbaseService, + ParticipationScoreService: tt.fields.ParticipationScoreService, + PublishedReceiptService: tt.fields.PublishedReceiptService, } got, err := bs.PopOffToBlock(tt.args.commonBlock) if (err != nil) != tt.wantErr { - t.Errorf("PopOffToBlock() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("BlockService.PopOffToBlock() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { - t.Errorf("PopOffToBlock() got = \n%v, want \n%v", got, tt.want) + t.Errorf("BlockService.PopOffToBlock() = %v, want %v", got, tt.want) } }) } diff --git a/core/service/mempoolCoreService.go b/core/service/mempoolCoreService.go index 91256c989..a54375cba 100644 --- a/core/service/mempoolCoreService.go +++ b/core/service/mempoolCoreService.go @@ -1,6 +1,7 @@ package service import ( + "bytes" "database/sql" "sort" "time" @@ -16,6 +17,7 @@ import ( "github.com/zoobc/zoobc-core/common/query" "github.com/zoobc/zoobc-core/common/transaction" "github.com/zoobc/zoobc-core/common/util" + commonUtils "github.com/zoobc/zoobc-core/common/util" coreUtil "github.com/zoobc/zoobc-core/core/util" "github.com/zoobc/zoobc-core/observer" "golang.org/x/crypto/sha3" @@ -44,6 +46,7 @@ type ( ) ([]*model.BatchReceipt, error) DeleteExpiredMempoolTransactions() error GetMempoolTransactionsWantToBackup(height uint32) ([]*model.MempoolTransaction, error) + BackupMempools(commonBlock *model.Block) error } // MempoolService contains all transactions in mempool plus a mux to manage locks in concurrency @@ -66,6 +69,7 @@ type ( ReceiptUtil coreUtil.ReceiptUtilInterface ReceiptService ReceiptServiceInterface TransactionCoreService TransactionCoreServiceInterface + BlockService BlockService } ) @@ -461,3 +465,77 @@ func (mps *MempoolService) GetMempoolTransactionsWantToBackup(height uint32) ([] return mempools, nil } + +func (mps *MempoolService) BackupMempools(commonBlock *model.Block) error { + + var ( + mempoolsBackupBytes *bytes.Buffer + mempoolsBackup []*model.MempoolTransaction + err error + ) + + mempoolsBackup, err = mps.GetMempoolTransactionsWantToBackup(commonBlock.Height) + if err != nil { + return err + } + mps.Logger.Warnf("mempool tx backup %d in total with block_height %d", len(mempoolsBackup), commonBlock.GetHeight()) + derivedQueries := query.GetDerivedQuery(mps.Chaintype) + err = mps.QueryExecutor.BeginTx() + if err != nil { + return err + } + + mempoolsBackupBytes = bytes.NewBuffer([]byte{}) + + for _, mempool := range mempoolsBackup { + var ( + tx *model.Transaction + txType transaction.TypeAction + ) + tx, err := mps.TransactionUtil.ParseTransactionBytes(mempool.GetTransactionBytes(), true) + if err != nil { + return err + } + txType, err = mps.ActionTypeSwitcher.GetTransactionType(tx) + if err != nil { + return err + } + + err = mps.TransactionCoreService.UndoApplyUnconfirmedTransaction(txType) + if err != nil { + _ = mps.QueryExecutor.RollbackTx() + return err + } + + /* + mempoolsBackupBytes format is + [...{4}byteSize,{bytesSize}transactionBytes] + */ + sizeMempool := uint32(len(mempool.GetTransactionBytes())) + mempoolsBackupBytes.Write(commonUtils.ConvertUint32ToBytes(sizeMempool)) + mempoolsBackupBytes.Write(mempool.GetTransactionBytes()) + } + + for _, dQuery := range derivedQueries { + queries := dQuery.Rollback(commonBlock.Height) + err = mps.QueryExecutor.ExecuteTransactions(queries) + if err != nil { + _ = mps.QueryExecutor.RollbackTx() + return err + } + } + err = mps.QueryExecutor.CommitTx() + if err != nil { + return err + } + + if mempoolsBackupBytes.Len() > 0 { + kvdbMempoolsBackupKey := commonUtils.GetKvDbMempoolDBKey(mps.BlockService.GetChainType()) + err = mps.KVExecutor.Insert(kvdbMempoolsBackupKey, mempoolsBackupBytes.Bytes(), int(constant.KVDBMempoolsBackupExpiry)) + if err != nil { + return err + } + } + + return nil +} From e391d9cd6a4c9c8a5d3628cc92a5b787f20c2e75 Mon Sep 17 00:00:00 2001 From: Nawi Kartini <33143058+nawikart@users.noreply.github.com> Date: Thu, 14 May 2020 09:21:10 +0800 Subject: [PATCH 2/6] Unit test api handler account dataset (#821) * GetAccountDatasets * GetAccountDataset --- api/handler/accountDatasetHandler_test.go | 188 ++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 api/handler/accountDatasetHandler_test.go diff --git a/api/handler/accountDatasetHandler_test.go b/api/handler/accountDatasetHandler_test.go new file mode 100644 index 000000000..a5497267f --- /dev/null +++ b/api/handler/accountDatasetHandler_test.go @@ -0,0 +1,188 @@ +package handler + +import ( + "context" + "errors" + "reflect" + "testing" + + "github.com/zoobc/zoobc-core/api/service" + "github.com/zoobc/zoobc-core/common/model" +) + +type ( + mockGetAccountDatasetsError struct { + service.AccountDatasetServiceInterface + } + mockGetAccountDatasetsSuccess struct { + service.AccountDatasetServiceInterface + } +) + +func (*mockGetAccountDatasetsError) GetAccountDatasets(request *model.GetAccountDatasetsRequest) (*model.GetAccountDatasetsResponse, error) { + return nil, errors.New("Error GetAccountDatasets") +} +func (*mockGetAccountDatasetsSuccess) GetAccountDatasets(request *model.GetAccountDatasetsRequest) (*model.GetAccountDatasetsResponse, error) { + return &model.GetAccountDatasetsResponse{}, nil +} + +func TestAccountDatasetHandler_GetAccountDatasets(t *testing.T) { + type fields struct { + Service service.AccountDatasetServiceInterface + } + type args struct { + in0 context.Context + request *model.GetAccountDatasetsRequest + } + tests := []struct { + name string + fields fields + args args + want *model.GetAccountDatasetsResponse + wantErr bool + }{ + { + name: "GetAccountDatasets:LimitExceeded", + args: args{ + request: &model.GetAccountDatasetsRequest{ + Pagination: &model.Pagination{ + Limit: uint32(600), + }, + }, + }, + want: nil, + wantErr: true, + }, + { + name: "GetAccountDatasets:Error", + fields: fields{ + Service: &mockGetAccountDatasetsError{}, + }, + args: args{ + request: &model.GetAccountDatasetsRequest{ + Pagination: &model.Pagination{ + Limit: uint32(250), + }, + }, + }, + want: nil, + wantErr: true, + }, + { + name: "GetAccountDatasets:Success", + fields: fields{ + Service: &mockGetAccountDatasetsSuccess{}, + }, + args: args{ + request: &model.GetAccountDatasetsRequest{ + Pagination: &model.Pagination{ + Limit: uint32(250), + }, + }, + }, + want: &model.GetAccountDatasetsResponse{}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + adh := &AccountDatasetHandler{ + Service: tt.fields.Service, + } + got, err := adh.GetAccountDatasets(tt.args.in0, tt.args.request) + if (err != nil) != tt.wantErr { + t.Errorf("AccountDatasetHandler.GetAccountDatasets() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("AccountDatasetHandler.GetAccountDatasets() = %v, want %v", got, tt.want) + } + }) + } +} + +type ( + mockGetAccountDatasetError struct { + service.AccountDatasetServiceInterface + } + mockGetAccountDatasetSuccess struct { + service.AccountDatasetServiceInterface + } +) + +func (*mockGetAccountDatasetError) GetAccountDataset(request *model.GetAccountDatasetRequest) (*model.AccountDataset, error) { + return nil, errors.New("Error GetAccountDataset") +} +func (*mockGetAccountDatasetSuccess) GetAccountDataset(request *model.GetAccountDatasetRequest) (*model.AccountDataset, error) { + return &model.AccountDataset{}, nil +} + +func TestAccountDatasetHandler_GetAccountDataset(t *testing.T) { + type fields struct { + Service service.AccountDatasetServiceInterface + } + type args struct { + in0 context.Context + request *model.GetAccountDatasetRequest + } + tests := []struct { + name string + fields fields + args args + want *model.AccountDataset + wantErr bool + }{ + { + name: "GetAccountDataset:InvalidRequest", + args: args{ + request: &model.GetAccountDatasetRequest{ + RecipientAccountAddress: "", + Property: "", + }, + }, + want: nil, + wantErr: true, + }, + { + name: "GetAccountDataset:Error", + fields: fields{ + Service: &mockGetAccountDatasetError{}, + }, + args: args{ + request: &model.GetAccountDatasetRequest{ + RecipientAccountAddress: "H1ftvv3n6CF5NDzdjmZKLRrBg6yPKHXpmatVUhQ5NWYx", + }, + }, + want: nil, + wantErr: true, + }, + { + name: "GetAccountDataset:Success", + fields: fields{ + Service: &mockGetAccountDatasetSuccess{}, + }, + args: args{ + request: &model.GetAccountDatasetRequest{ + RecipientAccountAddress: "H1ftvv3n6CF5NDzdjmZKLRrBg6yPKHXpmatVUhQ5NWYx", + }, + }, + want: &model.AccountDataset{}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + adh := &AccountDatasetHandler{ + Service: tt.fields.Service, + } + got, err := adh.GetAccountDataset(tt.args.in0, tt.args.request) + if (err != nil) != tt.wantErr { + t.Errorf("AccountDatasetHandler.GetAccountDataset() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("AccountDatasetHandler.GetAccountDataset() = %v, want %v", got, tt.want) + } + }) + } +} From b475b7159e45580879c7bfb20be91e129a716510 Mon Sep 17 00:00:00 2001 From: Ariasa Ketut Date: Thu, 14 May 2020 09:36:29 +0800 Subject: [PATCH 3/6] Expire musig pending transaction (#828) * ignore .bak like genesis.go.bak :) * expiring pending transactions by height and undo apply unconfirmed * update readme about multis sig generator * not allow filter by pending and expired * finish query logic to, so now (block_height+min_rollback) = current_block_height * defer func for rows close and matches the others about query logic * add log instance into transaction core service, ignore log level Co-authored-by: Gede Sukra Widhyawan Co-authored-by: Andy Shi --- .gitignore | 4 +- api/handler/multisigHandler.go | 10 +- cmd/block/blockGenerator.go | 4 + cmd/readme.md | 44 +++-- common/query/multisignatureInfoQuery.go | 2 +- common/query/pendingTransactionQuery.go | 17 ++ common/query/pendingTransactionQuery_test.go | 48 ++++++ core/service/blockMainService.go | 9 +- core/service/blockMainService_test.go | 44 ++++- core/service/mempoolCoreService_test.go | 8 + core/service/mempoolServiceUtil_test.go | 7 +- core/service/transactionCoreService.go | 160 ++++++++++++++++--- main.go | 18 +++ 13 files changed, 328 insertions(+), 47 deletions(-) diff --git a/.gitignore b/.gitignore index 4e0245a09..3ee901e98 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,6 @@ accounts.txt .editorconfig .manual release/ -github.token \ No newline at end of file +github.token +*.back +*.bak diff --git a/api/handler/multisigHandler.go b/api/handler/multisigHandler.go index 560bc97ed..b3913a877 100644 --- a/api/handler/multisigHandler.go +++ b/api/handler/multisigHandler.go @@ -3,12 +3,10 @@ package handler import ( "context" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "github.com/zoobc/zoobc-core/api/service" - "github.com/zoobc/zoobc-core/common/model" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) type ( @@ -28,8 +26,8 @@ func (msh *MultisigHandler) GetPendingTransactions( req.Pagination.OrderField = "block_height" req.Pagination.OrderBy = model.OrderBy_DESC } - result, err := msh.MultisigService.GetPendingTransactions(req) - return result, err + + return msh.MultisigService.GetPendingTransactions(req) } func (msh *MultisigHandler) GetPendingTransactionDetailByTransactionHash( diff --git a/cmd/block/blockGenerator.go b/cmd/block/blockGenerator.go index 796da680a..af0a53b66 100644 --- a/cmd/block/blockGenerator.go +++ b/cmd/block/blockGenerator.go @@ -186,9 +186,13 @@ func initialize( receiptUtil, publishedReceiptUtil, service.NewTransactionCoreService( + nil, queryExecutor, + nil, + nil, query.NewTransactionQuery(chainType), nil, + nil, ), nil, nil, diff --git a/cmd/readme.md b/cmd/readme.md index fde6a272f..ba9f6ce3e 100644 --- a/cmd/readme.md +++ b/cmd/readme.md @@ -22,12 +22,14 @@ Command line interface to as a utility tools to develop the zoobc system. - `go run main {command} --help` to see to see available subcommands and flags - `go run main {command} {subcommand} --help` to see to see available subcommands and flags of subcommand +## Transaction Commands ### Transaction general flag - `--output` to provide generated ouput type. Example: `--ouput bytes` - `--version` to provide version of transaction. Example: `--version 1` - `--timestamp` to provide timestamp of trasaction. Example: `--timestamp 1234567` - `--sender-seed` to provide the seed of sender transaction. Example: `--sender-seed "concur vocalist rotten busload gap quote stinging undiluted surfer goofiness deviation starved"` +- `--sender-address` transaction's sender address - `--recipient` provide recepient transaction. Example `--recipient VZvYd80p5S-rxSNQmMZwYXC7LyAzBmcfcj4MUUAdudWM` - `--fee` to provide fee transaction, Example `--fee 1` - `--post` to define automate post transaction or not. Example: `-post true` @@ -40,7 +42,7 @@ Command line interface to as a utility tools to develop the zoobc system. go run main.go generate transaction send-money --timestamp 1257894000 --sender-seed "concur vocalist rotten busload gap quote stinging undiluted surfer goofiness deviation starved" --recipient VZvYd80p5S-rxSNQmMZwYXC7LyAzBmcfcj4MUUAdudWM --amount 5000000000 ``` -#### Transaction send money escrow, set flag `--escrow true` and 3 more fields: `--approver-address`, `--timeout`, `--commission` and `--instruction` +### Transaction send money escrow, set flag `--escrow true` and 3 more fields: `--approver-address`, `--timeout`, `--commission` and `--instruction` ```bash go run main.go generate transaction send-money --escrow true --approver-address BCZEGOb3WNx3fDOVf9ZS4EjvOIv_UeW4TVBQJ_6tHKlE --timeout 200 --sender-seed "execute beach inflict session course dance vanish cover lawsuit earth casino fringe waste warfare also habit skull donate window cannon scene salute dawn good" --amount 1111 --commission 111 --instruction "Check amount should be 111" --recipient nK_ouxdDDwuJiogiDAi_zs1LqeN7f5ZsXbFtXGqGc0Pd @@ -88,12 +90,34 @@ go run main.go generate transaction remove-account-dataset --timestamp 125789400 go run main.go generate transaction escrow-approval --transaction-id -2546596465476625657 --approval true --sender-seed "concur vocalist rotten busload gap quote stinging undiluted surfer goofiness deviation starved" --fee 111 ``` +### Transaction Multi Signatures +```bash +Flags: + --address-signatures stringToString address:signature list --address1='signature1' --address2='signature2' (default []) + --addresses strings list of participants --addresses='address1,address2' + -h, --help help for multi-signature + --min-signature uint32 minimum number of signature required for the transaction to be valid + --nonce int random number / access code for the multisig info + --transaction-hash string hash of transaction being signed by address-signature list hex + --unsigned-transaction string hex string of the unsigned transaction bytes +``` +For the multi signature transaction let say want to send money with multisig account, need to do this steps: +1. Generate transaction send money, make sure with argument `--hash`. It will be `--unsigned-transaction` value on multi signature generator. +2. Sign the transaction to get the transaction hash, and it will be `--transcation-has` and the last the `signature-hex` will be as `--address-signatures` value on multi signature generator.
+ +So the completed comment it will be: +```bash +go run main.go generate transaction multi-signature --sender-seed="execute beach inflict session course dance vanish cover lawsuit earth casino fringe waste warfare also habit skull donate window cannon scene salute dawn good" --unsigned-transaction="01000000012ba5ba5e000000002c000000486c5a4c683356636e4e6c764279576f417a584f51326a416c77464f69794f395f6e6a49336f7135596768612c000000486c38393154655446784767574f57664f4f464b59725f586468584e784f384a4b38576e4d4a56366738614c41420f0000000000080000000600000000000000000000000000000000000000000000000000000000000000" --transaction-hash="21ddbdada9903da81bf17dba6569ff7e2665fec38760c7f6636419ee30da65b0" --address-signatures="HlZLh3VcnNlvByWoAzXOQ2jAlwFOiyO9_njI3oq5Ygha=00000000b4efe21822c9d63818d8d19f6c608d917b2237426d1157b4e6689b22ce6c256ccf8ec8e2c1016ab09dd4ef2b01191fe2df70b7a123fec7115d7afd5a938f9b0a" +``` + +## Block Commands ### Block Generating Fake Blocks ```bash go run main.go generate block fake-blocks --numberOfBlocks=1000 --blocksmithSecretPhrase='sprinkled sneak species pork outpost thrift unwind cheesy vexingly dizzy neurology neatness' --out='../resource/zoobc.db' ``` +## Account Commands ### Account Generate Using Ed25519 Algorithm ```bash @@ -114,30 +138,28 @@ go run main.go generate account bitcoin --seed "concur vocalist rotten busload g go run main.go generate account multisig --addresses "BCZnSfqpP5tqFQlMTYkDeBVFWnbyVK7vLr5ORFpTjgtN" --addresses "BCZD_VxfO2S9aziIL3cn_cXW7uPDVPOrnXuP98GEAUC7" --addresses "BCZKLvgUYZ1KKx-jtF9KoJskjVPvB9jpIjfzzI6zDW0J" —-min-sigs=2 --nonce=3 ``` +## Other Commands +```bash go run main.go genesis generate +``` outputs cmd/genesis.go.new and cmd/cluster_config.json ```bash - ### Genesis Generate From cmd/genesisblock/preRegisteredNodes.json and resource/zoobc.db ``` - +```bash go run main.go genesis generate -w +``` outputs cmd/genesis.go.new and cmd/cluster_config.json -```bash - -### Genesis Generate From cmd/genesisblock/preRegisteredNodes.json and resource/zoobc.db, plus n random nodes registrations -``` +### Genesis Generate From cmd/genesisblock/preRegisteredNodes.json and resource/zoobc.db, plus n random nodes registrations +```bash go run main.go genesis generate -w -n 10 -outputs cmd/genesis.go.new and cmd/cluster_config.json - -``` - ``` +outputs cmd/genesis.go.new and cmd/cluster_config.json ### Generate Proof of Ownership Node Registry diff --git a/common/query/multisignatureInfoQuery.go b/common/query/multisignatureInfoQuery.go index a76e5867f..8a22246b9 100644 --- a/common/query/multisignatureInfoQuery.go +++ b/common/query/multisignatureInfoQuery.go @@ -63,7 +63,7 @@ func (msi *MultisignatureInfoQuery) GetMultisignatureInfoByAddress( } } -// InsertPendingSignature inserts a new pending transaction into DB +// InsertMultisignatureInfo inserts a new pending transaction into DB func (msi *MultisignatureInfoQuery) InsertMultisignatureInfo(multisigInfo *model.MultiSignatureInfo) [][]interface{} { var queries [][]interface{} insertQuery := fmt.Sprintf("INSERT OR REPLACE INTO %s (%s) VALUES(%s)", diff --git a/common/query/pendingTransactionQuery.go b/common/query/pendingTransactionQuery.go index b40560317..6f264a71c 100644 --- a/common/query/pendingTransactionQuery.go +++ b/common/query/pendingTransactionQuery.go @@ -5,6 +5,8 @@ import ( "fmt" "strings" + "github.com/zoobc/zoobc-core/common/constant" + "github.com/zoobc/zoobc-core/common/model" ) @@ -22,6 +24,7 @@ type ( ) ( str string, args []interface{}, ) + GetPendingTransactionsExpireByHeight(blockHeight uint32) (str string, args []interface{}) InsertPendingTransaction(pendingTx *model.PendingTransaction) [][]interface{} Scan(pendingTx *model.PendingTransaction, row *sql.Row) error ExtractModel(pendingTx *model.PendingTransaction) []interface{} @@ -103,6 +106,20 @@ func (ptq *PendingTransactionQuery) GetPendingTransactionsBySenderAddress( } } +// GetPendingTransactionsExpireByHeight presents query to get pending_transactions that was expire by block_height +func (ptq *PendingTransactionQuery) GetPendingTransactionsExpireByHeight(currentHeight uint32) (str string, args []interface{}) { + return fmt.Sprintf( + "SELECT %s FROM %s WHERE block_height = ? AND status = ? AND latest = ?", + strings.Join(ptq.Fields, ", "), + ptq.getTableName(), + ), + []interface{}{ + currentHeight - constant.MinRollbackBlocks, + model.PendingTransactionStatus_PendingTransactionPending, + true, + } +} + // InsertPendingTransaction inserts a new pending transaction into DB func (ptq *PendingTransactionQuery) InsertPendingTransaction(pendingTx *model.PendingTransaction) [][]interface{} { var queries [][]interface{} diff --git a/common/query/pendingTransactionQuery_test.go b/common/query/pendingTransactionQuery_test.go index b0bffd8b1..290401acf 100644 --- a/common/query/pendingTransactionQuery_test.go +++ b/common/query/pendingTransactionQuery_test.go @@ -567,3 +567,51 @@ func TestPendingTransactionQuery_TrimDataBeforeSnapshot(t *testing.T) { }) } } + +func TestPendingTransactionQuery_GetPendingTransactionsExpireByHeight(t *testing.T) { + type fields struct { + Fields []string + TableName string + } + type args struct { + currentHeight uint32 + } + tests := []struct { + name string + fields fields + args args + wantStr string + wantArgs []interface{} + }{ + { + name: "WantSuccess", + fields: fields(*NewPendingTransactionQuery()), + args: args{ + currentHeight: 1000, + }, + wantStr: "SELECT sender_address, transaction_hash, transaction_bytes, status, block_height, latest " + + "FROM pending_transaction WHERE block_height = ? AND status = ? AND latest = ?", + wantArgs: []interface{}{ + uint32(1000) - constant.MinRollbackBlocks, + model.PendingTransactionStatus_PendingTransactionPending, + true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ptq := &PendingTransactionQuery{ + Fields: tt.fields.Fields, + TableName: tt.fields.TableName, + } + gotStr, gotArgs := ptq.GetPendingTransactionsExpireByHeight(tt.args.currentHeight) + if gotStr != tt.wantStr { + t.Errorf("GetPendingTransactionsExpireByHeight() gotStr = %v, want %v", gotStr, tt.wantStr) + return + } + if !reflect.DeepEqual(gotArgs, tt.wantArgs) { + t.Errorf("GetPendingTransactionsExpireByHeight() gotArgs = %v, want %v", gotArgs, tt.wantArgs) + } + }) + } +} diff --git a/core/service/blockMainService.go b/core/service/blockMainService.go index 87b6aa764..abc050f60 100644 --- a/core/service/blockMainService.go +++ b/core/service/blockMainService.go @@ -408,11 +408,18 @@ func (bs *BlockService) PushBlock(previousBlock, block *model.Block, broadcast, return err } - // Respecting Expiring escrow before push block process + /* + Expiring Process: expiring the the transactions that affected by current block height. + Respecting Expiring escrow and multi signature transaction before push block process + */ err = bs.TransactionCoreService.ExpiringEscrowTransactions(block.GetHeight(), true) if err != nil { return blocker.NewBlocker(blocker.BlockErr, err.Error()) } + err = bs.TransactionCoreService.ExpiringPendingTransactions(block.GetHeight(), true) + if err != nil { + return blocker.NewBlocker(blocker.BlockErr, err.Error()) + } blockInsertQuery, blockInsertValue := bs.BlockQuery.InsertBlock(block) err = bs.QueryExecutor.ExecuteTransaction(blockInsertQuery, blockInsertValue...) diff --git a/core/service/blockMainService_test.go b/core/service/blockMainService_test.go index df99437c3..8ce4f2371 100644 --- a/core/service/blockMainService_test.go +++ b/core/service/blockMainService_test.go @@ -335,10 +335,10 @@ func (*mockQueryExecutorSuccess) BeginTx() error { return nil } func (*mockQueryExecutorSuccess) RollbackTx() error { return nil } -func (*mockQueryExecutorSuccess) ExecuteTransaction(qStr string, args ...interface{}) error { +func (*mockQueryExecutorSuccess) ExecuteTransaction(string, ...interface{}) error { return nil } -func (*mockQueryExecutorSuccess) ExecuteTransactions(queries [][]interface{}) error { +func (*mockQueryExecutorSuccess) ExecuteTransactions([][]interface{}) error { return nil } func (*mockQueryExecutorSuccess) CommitTx() error { return nil } @@ -577,6 +577,9 @@ func (*mockQueryExecutorSuccess) ExecuteSelect(qe string, tx bool, args ...inter mockTransaction.GetSenderAccountAddress(), mockTransaction.GetRecipientAccountAddress(), )) + case "SELECT sender_address, transaction_hash, transaction_bytes, status, block_height, latest " + + "FROM pending_transaction WHERE block_height = ? AND status = ? AND latest = ?": + mock.ExpectQuery(regexp.QuoteMeta(qe)).WillReturnRows(mock.NewRows(query.NewPendingTransactionQuery().Fields)) // which is escrow expiration process default: mockRows := sqlmock.NewRows(query.NewEscrowTransactionQuery().Fields) @@ -935,11 +938,7 @@ func (*mockPushBlockCoinbaseLotteryWinnersSuccess) CoinbaseLotteryWinners(blocks return []string{}, nil } -func (*mockPushBlockBlocksmithServiceSuccess) RewardBlocksmithAccountAddresses( - blocksmithAccountAddresses []string, - totalReward, blockTimestamp int64, - height uint32, -) error { +func (*mockPushBlockBlocksmithServiceSuccess) RewardBlocksmithAccountAddresses([]string, int64, int64, uint32) error { return nil } @@ -1056,9 +1055,15 @@ func TestBlockService_PushBlock(t *testing.T) { CoinbaseService: &mockPushBlockCoinbaseLotteryWinnersSuccess{}, BlocksmithService: &mockPushBlockBlocksmithServiceSuccess{}, TransactionCoreService: NewTransactionCoreService( + log.New(), &mockQueryExecutorSuccess{}, + &transaction.TypeSwitcher{ + Executor: &mockQueryExecutorSuccess{}, + }, + &transaction.Util{}, query.NewTransactionQuery(&chaintype.MainChain{}), query.NewEscrowTransactionQuery(), + query.NewPendingTransactionQuery(), ), PublishedReceiptService: &mockPushBlockPublishedReceiptServiceSuccess{}, }, @@ -1116,9 +1121,13 @@ func TestBlockService_PushBlock(t *testing.T) { CoinbaseService: &mockPushBlockCoinbaseLotteryWinnersSuccess{}, BlocksmithService: &mockPushBlockBlocksmithServiceSuccess{}, TransactionCoreService: NewTransactionCoreService( + log.New(), &mockQueryExecutorSuccess{}, + &transaction.TypeSwitcher{Executor: &mockQueryExecutorSuccess{}}, + &transaction.Util{}, query.NewTransactionQuery(&chaintype.MainChain{}), query.NewEscrowTransactionQuery(), + query.NewPendingTransactionQuery(), ), PublishedReceiptService: &mockPushBlockPublishedReceiptServiceSuccess{}, }, @@ -1176,9 +1185,15 @@ func TestBlockService_PushBlock(t *testing.T) { CoinbaseService: &mockPushBlockCoinbaseLotteryWinnersSuccess{}, BlocksmithService: &mockPushBlockBlocksmithServiceSuccess{}, TransactionCoreService: NewTransactionCoreService( + log.New(), &mockQueryExecutorSuccess{}, + &transaction.TypeSwitcher{ + Executor: &mockQueryExecutorSuccess{}, + }, + &transaction.Util{}, query.NewTransactionQuery(&chaintype.MainChain{}), query.NewEscrowTransactionQuery(), + query.NewPendingTransactionQuery(), ), PublishedReceiptService: &mockPushBlockPublishedReceiptServiceSuccess{}, }, @@ -1236,9 +1251,15 @@ func TestBlockService_PushBlock(t *testing.T) { CoinbaseService: &mockPushBlockCoinbaseLotteryWinnersSuccess{}, BlocksmithService: &mockPushBlockBlocksmithServiceSuccess{}, TransactionCoreService: NewTransactionCoreService( + log.New(), &mockQueryExecutorSuccess{}, + &transaction.TypeSwitcher{ + Executor: &mockQueryExecutorSuccess{}, + }, + &transaction.Util{}, query.NewTransactionQuery(&chaintype.MainChain{}), query.NewEscrowTransactionQuery(), + query.NewPendingTransactionQuery(), ), PublishedReceiptService: &mockPushBlockPublishedReceiptServiceSuccess{}, }, @@ -1900,9 +1921,15 @@ func TestBlockService_AddGenesis(t *testing.T) { BlockPoolService: &mockBlockPoolServiceNoDuplicate{}, Logger: log.New(), TransactionCoreService: NewTransactionCoreService( + log.New(), &mockQueryExecutorSuccess{}, + &transaction.TypeSwitcher{ + Executor: &mockQueryExecutorSuccess{}, + }, + &transaction.Util{}, query.NewTransactionQuery(&chaintype.MainChain{}), query.NewEscrowTransactionQuery(), + query.NewPendingTransactionQuery(), ), PublishedReceiptService: &mockAddGenesisPublishedReceiptServiceSuccess{}, }, @@ -4201,6 +4228,9 @@ func (*mockExecutorBlockPopSuccess) ExecuteSelect(qStr string, tx bool, args ... "transaction_index FROM \"transaction\" WHERE block_id = ? ORDER BY transaction_index ASC": mock.ExpectQuery(regexp.QuoteMeta(qStr)).WillReturnRows( sqlmock.NewRows(transactionQ.Fields)) + case "SELECT sender_address, transaction_hash, transaction_bytes, status, block_height, latest FROM pending_transaction " + + "WHERE (block_height+?) = ? AND status = ? AND latest = ?": + mock.ExpectQuery(regexp.QuoteMeta(qStr)).WillReturnRows(mock.NewRows(query.NewPendingTransactionQuery().Fields)) } return db.Query(qStr) diff --git a/core/service/mempoolCoreService_test.go b/core/service/mempoolCoreService_test.go index 063220b2c..ecf9745c3 100644 --- a/core/service/mempoolCoreService_test.go +++ b/core/service/mempoolCoreService_test.go @@ -471,9 +471,13 @@ func TestMempoolService_DeleteExpiredMempoolTransactions(t *testing.T) { Executor: &mockQueryExecutorDeleteExpiredMempoolTransactions{}, }, TransactionCoreService: NewTransactionCoreService( + log.New(), &mockQueryExecutorDeleteExpiredMempoolTransactions{}, + nil, + nil, query.NewTransactionQuery(&chaintype.MainChain{}), nil, + nil, ), }, wantErr: false, @@ -980,9 +984,13 @@ func TestMempoolService_ReceivedTransaction(t *testing.T) { ActionTypeSwitcher: &mockActionTypeSwitcherSuccess{}, Observer: observer.NewObserver(), TransactionCoreService: NewTransactionCoreService( + log.New(), &mockQueryExecutorDeleteExpiredMempoolTransactions{}, + nil, + nil, query.NewTransactionQuery(&chaintype.MainChain{}), nil, + nil, ), }, args: args{}, diff --git a/core/service/mempoolServiceUtil_test.go b/core/service/mempoolServiceUtil_test.go index 8b90d4894..a6f03904c 100644 --- a/core/service/mempoolServiceUtil_test.go +++ b/core/service/mempoolServiceUtil_test.go @@ -8,6 +8,8 @@ import ( "strings" "testing" + "github.com/sirupsen/logrus" + "github.com/DATA-DOG/go-sqlmock" "github.com/zoobc/zoobc-core/common/chaintype" "github.com/zoobc/zoobc-core/common/model" @@ -245,9 +247,12 @@ func TestMempoolService_ValidateMempoolTransaction(t *testing.T) { AccountBalanceQuery: query.NewAccountBalanceQuery(), TransactionQuery: query.NewTransactionQuery(&chaintype.MainChain{}), TransactionCoreService: NewTransactionCoreService( - &mockExecutorValidateMempoolTransactionSuccessNoRow{}, + logrus.New(), &mockExecutorValidateMempoolTransactionSuccessNoRow{}, + nil, + nil, query.NewTransactionQuery(&chaintype.MainChain{}), nil, + nil, ), }, args: args{ diff --git a/core/service/transactionCoreService.go b/core/service/transactionCoreService.go index 256b1d4ee..d4c7792f9 100644 --- a/core/service/transactionCoreService.go +++ b/core/service/transactionCoreService.go @@ -3,6 +3,7 @@ package service import ( "database/sql" + "github.com/sirupsen/logrus" "github.com/zoobc/zoobc-core/common/blocker" "github.com/zoobc/zoobc-core/common/model" "github.com/zoobc/zoobc-core/common/query" @@ -18,24 +19,37 @@ type ( UndoApplyUnconfirmedTransaction(txAction transaction.TypeAction) error ApplyConfirmedTransaction(txAction transaction.TypeAction, blockTimestamp int64) error ExpiringEscrowTransactions(blockHeight uint32, useTX bool) error + ExpiringPendingTransactions(blockHeight uint32, useTX bool) error } TransactionCoreService struct { - TransactionQuery query.TransactionQueryInterface - EscrowTransactionQuery query.EscrowTransactionQueryInterface - QueryExecutor query.ExecutorInterface + Log *logrus.Logger + QueryExecutor query.ExecutorInterface + TypeActionSwitcher transaction.TypeActionSwitcher + TransactionUtil transaction.UtilInterface + TransactionQuery query.TransactionQueryInterface + EscrowTransactionQuery query.EscrowTransactionQueryInterface + PendingTransactionQuery query.PendingTransactionQueryInterface } ) func NewTransactionCoreService( + log *logrus.Logger, queryExecutor query.ExecutorInterface, + typeActionSwitcher transaction.TypeActionSwitcher, + transactionUtil transaction.UtilInterface, transactionQuery query.TransactionQueryInterface, escrowTransactionQuery query.EscrowTransactionQueryInterface, + pendingTransactionQuery query.PendingTransactionQueryInterface, ) TransactionCoreServiceInterface { return &TransactionCoreService{ - TransactionQuery: transactionQuery, - EscrowTransactionQuery: escrowTransactionQuery, - QueryExecutor: queryExecutor, + Log: log, + QueryExecutor: queryExecutor, + TypeActionSwitcher: typeActionSwitcher, + TransactionUtil: transactionUtil, + TransactionQuery: transactionQuery, + EscrowTransactionQuery: escrowTransactionQuery, + PendingTransactionQuery: pendingTransactionQuery, } } @@ -86,21 +100,28 @@ func (tg *TransactionCoreService) ExpiringEscrowTransactions(blockHeight uint32, err error ) - escrowQ, escrowArgs := tg.EscrowTransactionQuery.GetEscrowTransactions(map[string]interface{}{ - "timeout": blockHeight, - "status": model.EscrowStatus_Pending, - "latest": 1, - }) - rows, err = tg.QueryExecutor.ExecuteSelect(escrowQ, useTX, escrowArgs...) - if err != nil { - return err - } - defer rows.Close() + err = func() error { + escrowQ, escrowArgs := tg.EscrowTransactionQuery.GetEscrowTransactions(map[string]interface{}{ + "timeout": blockHeight, + "status": model.EscrowStatus_Pending, + "latest": 1, + }) + rows, err = tg.QueryExecutor.ExecuteSelect(escrowQ, useTX, escrowArgs...) + if err != nil { + return err + } + defer rows.Close() - escrows, err = tg.EscrowTransactionQuery.BuildModels(rows) + escrows, err = tg.EscrowTransactionQuery.BuildModels(rows) + if err != nil { + return err + } + return nil + }() if err != nil { return err } + if len(escrows) > 0 { if !useTX { err = tg.QueryExecutor.BeginTx() @@ -119,19 +140,120 @@ func (tg *TransactionCoreService) ExpiringEscrowTransactions(blockHeight uint32, nEscrow.Status = model.EscrowStatus_Expired q := tg.EscrowTransactionQuery.InsertEscrowTransaction(escrow) err = tg.QueryExecutor.ExecuteTransactions(q) + if err != nil { + break + } + } + + if !useTX { + /* + Check the latest error is not nil, otherwise need to aborting the whole query transactions safety with rollBack. + And automatically unlock mutex + */ + if err != nil { + if rollbackErr := tg.QueryExecutor.RollbackTx(); rollbackErr != nil { + tg.Log.Errorf("Rollback fail: %s", rollbackErr.Error()) + } + return err + } + + err = tg.QueryExecutor.CommitTx() + if err != nil { + if rollbackErr := tg.QueryExecutor.RollbackTx(); rollbackErr != nil { + tg.Log.Errorf("Rollback fail: %s", rollbackErr.Error()) + } + return err + } + } + } + return nil +} + +// ExpiringPendingTransactions will set status to be expired caused by current block height +func (tg *TransactionCoreService) ExpiringPendingTransactions(blockHeight uint32, useTX bool) error { + var ( + pendingTransactions []*model.PendingTransaction + innerTransaction *model.Transaction + typeAction transaction.TypeAction + rows *sql.Rows + err error + ) + + err = func() error { + qy, qArgs := tg.PendingTransactionQuery.GetPendingTransactionsExpireByHeight(blockHeight) + rows, err = tg.QueryExecutor.ExecuteSelect(qy, useTX, qArgs...) + if err != nil { + return err + } + defer rows.Close() + + pendingTransactions, err = tg.PendingTransactionQuery.BuildModel(pendingTransactions, rows) + if err != nil { + return err + } + return nil + }() + if err != nil { + return err + } + + if len(pendingTransactions) > 0 { + if !useTX { + err = tg.QueryExecutor.BeginTx() if err != nil { return err } } + for _, pendingTransaction := range pendingTransactions { + + /** + SET PendingTransaction + 1. block height = current block height + 2. status = expired + */ + nPendingTransaction := pendingTransaction + nPendingTransaction.BlockHeight = blockHeight + nPendingTransaction.Status = model.PendingTransactionStatus_PendingTransactionExpired + q := tg.PendingTransactionQuery.InsertPendingTransaction(nPendingTransaction) + err = tg.QueryExecutor.ExecuteTransactions(q) + if err != nil { + break + } + // Do UndoApplyConfirmed + innerTransaction, err = tg.TransactionUtil.ParseTransactionBytes(nPendingTransaction.GetTransactionBytes(), false) + if err != nil { + break + } + typeAction, err = tg.TypeActionSwitcher.GetTransactionType(innerTransaction) + if err != nil { + break + } + err = typeAction.UndoApplyUnconfirmed() + if err != nil { + break + } + } if !useTX { + /* + Check the latest error is not nil, otherwise need to aborting the whole query transactions safety with rollBack. + And automatically unlock mutex + */ + if err != nil { + if rollbackErr := tg.QueryExecutor.RollbackTx(); rollbackErr != nil { + tg.Log.Errorf("Rollback fail: %s", rollbackErr.Error()) + } + return err + } err = tg.QueryExecutor.CommitTx() if err != nil { - if errRollback := tg.QueryExecutor.RollbackTx(); errRollback != nil { - return err + if rollbackErr := tg.QueryExecutor.RollbackTx(); rollbackErr != nil { + tg.Log.Errorf("Rollback fail: %s", rollbackErr.Error()) } + return err } } + return err } return nil } diff --git a/main.go b/main.go index f01a3988b..961d0b0d6 100644 --- a/main.go +++ b/main.go @@ -211,9 +211,15 @@ func init() { ) transactionCoreServiceIns = service.NewTransactionCoreService( + loggerCoreService, queryExecutor, + &transaction.TypeSwitcher{ + Executor: queryExecutor, + }, + &transaction.Util{}, query.NewTransactionQuery(mainchain), query.NewEscrowTransactionQuery(), + query.NewPendingTransactionQuery(), ) defaultSignatureType = crypto.NewEd25519Signature() @@ -602,9 +608,15 @@ func startMainchain() { Logger: loggerCoreService, TransactionUtil: transactionUtil, TransactionCorService: service.NewTransactionCoreService( + loggerCoreService, queryExecutor, + &transaction.TypeSwitcher{ + Executor: queryExecutor, + }, + &transaction.Util{}, query.NewTransactionQuery(mainchain), query.NewEscrowTransactionQuery(), + query.NewPendingTransactionQuery(), ), } mainchainSynchronizer = blockchainsync.NewBlockchainSyncService( @@ -691,9 +703,15 @@ func startSpinechain() { Logger: loggerCoreService, TransactionUtil: transactionUtil, TransactionCorService: service.NewTransactionCoreService( + loggerCoreService, queryExecutor, + &transaction.TypeSwitcher{ + Executor: queryExecutor, + }, + &transaction.Util{}, query.NewTransactionQuery(mainchain), query.NewEscrowTransactionQuery(), + query.NewPendingTransactionQuery(), ), } spinechainSynchronizer = blockchainsync.NewBlockchainSyncService( From bd7da4c80cec91ee0d0202a0e99fe0697c216b1a Mon Sep 17 00:00:00 2001 From: Nawi Kartini <33143058+nawikart@users.noreply.github.com> Date: Thu, 14 May 2020 09:44:37 +0800 Subject: [PATCH 4/6] Unit test api block handler (#830) * TestBlockHandler_GetBlock * TestBlockHandler_GetBlocks * fix captLocal * fix gocritic --- api/handler/blockHandler_test.go | 184 +++++++++++++++++++++++++++++++ common/schema | 2 +- 2 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 api/handler/blockHandler_test.go diff --git a/api/handler/blockHandler_test.go b/api/handler/blockHandler_test.go new file mode 100644 index 000000000..f7714ecc3 --- /dev/null +++ b/api/handler/blockHandler_test.go @@ -0,0 +1,184 @@ +package handler + +import ( + "context" + "errors" + "reflect" + "testing" + + "github.com/zoobc/zoobc-core/api/service" + "github.com/zoobc/zoobc-core/common/chaintype" + "github.com/zoobc/zoobc-core/common/model" +) + +type ( + mockGetBlockError struct { + service.BlockServiceInterface + } + mockGetBlockSuccess struct { + service.BlockServiceInterface + } +) + +func (*mockGetBlockError) GetBlockByID(chainType chaintype.ChainType, id int64) (*model.BlockExtendedInfo, error) { + return nil, errors.New("Error GetBlockByID") +} + +func (*mockGetBlockError) GetBlockByHeight(chainType chaintype.ChainType, height uint32) (*model.BlockExtendedInfo, error) { + return nil, errors.New("Error GetBlockByHeight") +} + +func (*mockGetBlockSuccess) GetBlockByID(chainType chaintype.ChainType, id int64) (*model.BlockExtendedInfo, error) { + return &model.BlockExtendedInfo{}, nil +} + +func (*mockGetBlockSuccess) GetBlockByHeight(chainType chaintype.ChainType, height uint32) (*model.BlockExtendedInfo, error) { + return &model.BlockExtendedInfo{}, nil +} + +func TestBlockHandler_GetBlock(t *testing.T) { + type fields struct { + Service service.BlockServiceInterface + } + type args struct { + ctx context.Context + req *model.GetBlockRequest + } + tests := []struct { + name string + fields fields + args args + want *model.BlockExtendedInfo + wantErr bool + }{ + { + name: "GetBlock:Error", + fields: fields{ + Service: &mockGetBlockError{}, + }, + args: args{ + req: &model.GetBlockRequest{ + ID: 1, + Height: 1, + }, + }, + want: nil, + wantErr: true, + }, + { + name: "GetBlock:Success", + fields: fields{ + Service: &mockGetBlockSuccess{}, + }, + args: args{ + req: &model.GetBlockRequest{ + ID: 1, + Height: 1, + }, + }, + want: &model.BlockExtendedInfo{}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + bs := &BlockHandler{ + Service: tt.fields.Service, + } + got, err := bs.GetBlock(tt.args.ctx, tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("BlockHandler.GetBlock() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("BlockHandler.GetBlock() = %v, want %v", got, tt.want) + } + }) + } +} + +type ( + mockGetBlocksError struct { + service.BlockServiceInterface + } + mockGetBlocksSucess struct { + service.BlockServiceInterface + } +) + +func (*mockGetBlocksError) GetBlocks(chainType chaintype.ChainType, count, height uint32) (*model.GetBlocksResponse, error) { + return nil, errors.New("Error GetBlocks") +} + +func (*mockGetBlocksSucess) GetBlocks(chainType chaintype.ChainType, count, height uint32) (*model.GetBlocksResponse, error) { + return &model.GetBlocksResponse{}, nil +} + +func TestBlockHandler_GetBlocks(t *testing.T) { + type fields struct { + Service service.BlockServiceInterface + } + type args struct { + ctx context.Context + req *model.GetBlocksRequest + } + tests := []struct { + name string + fields fields + args args + want *model.GetBlocksResponse + wantErr bool + }{ + { + name: "GetBlocks:LimitExceeded", + args: args{ + req: &model.GetBlocksRequest{ + Limit: 1000, + }, + }, + want: nil, + wantErr: true, + }, + { + name: "GetBlocks:Error", + args: args{ + req: &model.GetBlocksRequest{ + Limit: 500, + }, + }, + fields: fields{ + Service: &mockGetBlocksError{}, + }, + want: nil, + wantErr: true, + }, + { + name: "GetBlocks:Success", + args: args{ + req: &model.GetBlocksRequest{ + Limit: 500, + }, + }, + fields: fields{ + Service: &mockGetBlocksSucess{}, + }, + want: &model.GetBlocksResponse{}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + bs := &BlockHandler{ + Service: tt.fields.Service, + } + got, err := bs.GetBlocks(tt.args.ctx, tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("BlockHandler.GetBlocks() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("BlockHandler.GetBlocks() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/common/schema b/common/schema index 36078eb40..bc6bed528 160000 --- a/common/schema +++ b/common/schema @@ -1 +1 @@ -Subproject commit 36078eb4028ac749d7491cd8cc1e2965cd14944a +Subproject commit bc6bed5283da24c6ff7f868373ac425eb3565bd7 From 625cd5e8eedce20c6e5189a0510bc689e7002cc4 Mon Sep 17 00:00:00 2001 From: Nawi Kartini <33143058+nawikart@users.noreply.github.com> Date: Thu, 14 May 2020 10:01:21 +0800 Subject: [PATCH 5/6] Unit test api mempool handler (#831) * TestMempoolTransactionHandler_GetMempoolTransaction * TestMempoolTransactionHandler_GetMempoolTransactions --- api/handler/mempoolHandler_test.go | 148 +++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 api/handler/mempoolHandler_test.go diff --git a/api/handler/mempoolHandler_test.go b/api/handler/mempoolHandler_test.go new file mode 100644 index 000000000..564ccf726 --- /dev/null +++ b/api/handler/mempoolHandler_test.go @@ -0,0 +1,148 @@ +package handler + +import ( + "context" + "errors" + "reflect" + "testing" + + "github.com/zoobc/zoobc-core/api/service" + "github.com/zoobc/zoobc-core/common/chaintype" + "github.com/zoobc/zoobc-core/common/model" +) + +type ( + MockGetMempoolTransactionError struct { + service.MempoolTransactionServiceInterface + } + MockGetMempoolTransactionSuccess struct { + service.MempoolTransactionServiceInterface + } +) + +func (*MockGetMempoolTransactionError) GetMempoolTransaction(chainType chaintype.ChainType, params *model.GetMempoolTransactionRequest, +) (*model.GetMempoolTransactionResponse, error) { + return nil, errors.New("Error GetMempoolTransaction") +} + +func (*MockGetMempoolTransactionSuccess) GetMempoolTransaction(chainType chaintype.ChainType, params *model.GetMempoolTransactionRequest, +) (*model.GetMempoolTransactionResponse, error) { + return &model.GetMempoolTransactionResponse{}, nil +} + +func TestMempoolTransactionHandler_GetMempoolTransaction(t *testing.T) { + type fields struct { + Service service.MempoolTransactionServiceInterface + } + type args struct { + ctx context.Context + req *model.GetMempoolTransactionRequest + } + tests := []struct { + name string + fields fields + args args + want *model.GetMempoolTransactionResponse + wantErr bool + }{ + { + name: "GetMempoolTransaction:Error", + fields: fields{ + Service: &MockGetMempoolTransactionError{}, + }, + want: nil, + wantErr: true, + }, + { + name: "GetMempoolTransaction:Success", + fields: fields{ + Service: &MockGetMempoolTransactionSuccess{}, + }, + want: &model.GetMempoolTransactionResponse{}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + uth := &MempoolTransactionHandler{ + Service: tt.fields.Service, + } + got, err := uth.GetMempoolTransaction(tt.args.ctx, tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("MempoolTransactionHandler.GetMempoolTransaction() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MempoolTransactionHandler.GetMempoolTransaction() = %v, want %v", got, tt.want) + } + }) + } +} + +type ( + MockGetMempoolTransactionsError struct { + service.MempoolTransactionServiceInterface + } + MockGetMempoolTransactionsSuccess struct { + service.MempoolTransactionServiceInterface + } +) + +func (*MockGetMempoolTransactionsError) GetMempoolTransactions(chainType chaintype.ChainType, params *model.GetMempoolTransactionsRequest, +) (*model.GetMempoolTransactionsResponse, error) { + return nil, errors.New("Error GetMempoolTransactions") +} + +func (*MockGetMempoolTransactionsSuccess) GetMempoolTransactions(chainType chaintype.ChainType, params *model.GetMempoolTransactionsRequest, +) (*model.GetMempoolTransactionsResponse, error) { + return &model.GetMempoolTransactionsResponse{}, nil +} + +func TestMempoolTransactionHandler_GetMempoolTransactions(t *testing.T) { + type fields struct { + Service service.MempoolTransactionServiceInterface + } + type args struct { + ctx context.Context + req *model.GetMempoolTransactionsRequest + } + tests := []struct { + name string + fields fields + args args + want *model.GetMempoolTransactionsResponse + wantErr bool + }{ + { + name: "GetMempoolTransactions:Error", + fields: fields{ + Service: &MockGetMempoolTransactionsError{}, + }, + want: nil, + wantErr: true, + }, + { + name: "GetMempoolTransactions:Success", + fields: fields{ + Service: &MockGetMempoolTransactionsSuccess{}, + }, + want: &model.GetMempoolTransactionsResponse{}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + uth := &MempoolTransactionHandler{ + Service: tt.fields.Service, + } + got, err := uth.GetMempoolTransactions(tt.args.ctx, tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("MempoolTransactionHandler.GetMempoolTransactions() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MempoolTransactionHandler.GetMempoolTransactions() = %v, want %v", got, tt.want) + } + }) + } +} From c20ba7bc5510ee025e168b5d108f6bb4835a979f Mon Sep 17 00:00:00 2001 From: Andy Shi Date: Thu, 14 May 2020 10:10:58 +0800 Subject: [PATCH 6/6] 829 download escrow transaction (#834) * #832 add one more persistance time cycle to check to avoid node stuck in small network * #829 include escrow detail of transaction in GetTransactionByBlockID * #829 include escrow detail of transaction in GetTransactionByBlockID * #833 add copy of block to broadcast before deleting the transactions * #829 fix test * #829 add test for escrow transaction query * #833 broadcasted wrong variable * #829 refactor escrow transaction query to be more generic --- common/query/escrowTransactionQuery.go | 15 +++++ common/query/escrowTransactionQuery_test.go | 13 +++++ core/service/blockMainService.go | 9 ++- core/service/transactionCoreService.go | 51 +++++++++++++--- core/service/transactionCoreService_test.go | 58 ++++++++++++++++--- core/smith/strategy/blocksmithStrategyMain.go | 17 ++++-- 6 files changed, 141 insertions(+), 22 deletions(-) diff --git a/common/query/escrowTransactionQuery.go b/common/query/escrowTransactionQuery.go index 0b56e7cc0..dc5e96980 100644 --- a/common/query/escrowTransactionQuery.go +++ b/common/query/escrowTransactionQuery.go @@ -20,6 +20,9 @@ type ( InsertEscrowTransaction(escrow *model.Escrow) [][]interface{} GetLatestEscrowTransactionByID(int64) (string, []interface{}) GetEscrowTransactions(fields map[string]interface{}) (string, []interface{}) + GetEscrowTransactionsByTransactionIdsAndStatus( + transactionIds []string, status model.EscrowStatus, + ) string ExpiringEscrowTransactions(blockHeight uint32) (string, []interface{}) ExtractModel(*model.Escrow) []interface{} BuildModels(*sql.Rows) ([]*model.Escrow, error) @@ -123,6 +126,18 @@ func (et *EscrowTransactionQuery) ExpiringEscrowTransactions(blockHeight uint32) } } +func (et *EscrowTransactionQuery) GetEscrowTransactionsByTransactionIdsAndStatus( + transactionIds []string, status model.EscrowStatus, +) string { + return fmt.Sprintf( + "SELECT %s FROM %s WHERE id IN (%s) AND status = %d", + strings.Join(et.Fields, ", "), + et.getTableName(), + strings.Join(transactionIds, ", "), + status, + ) +} + // ExtractModel will extract values of escrow as []interface{} func (et *EscrowTransactionQuery) ExtractModel(escrow *model.Escrow) []interface{} { return []interface{}{ diff --git a/common/query/escrowTransactionQuery_test.go b/common/query/escrowTransactionQuery_test.go index 8e305784e..8769d179f 100644 --- a/common/query/escrowTransactionQuery_test.go +++ b/common/query/escrowTransactionQuery_test.go @@ -2,6 +2,7 @@ package query import ( "database/sql" + "fmt" "reflect" "sort" "strings" @@ -534,3 +535,15 @@ func TestEscrowTransactionQuery_TrimDataBeforeSnapshot(t *testing.T) { }) } } + +func TestEscrowTransactionQuery_GetEscrowTransactionsByTransactionIdsAndStatus(t *testing.T) { + t.Run("GetPendingEscrowTransactionsByTransactionIds", func(t *testing.T) { + escrowQuery := NewEscrowTransactionQuery() + query := escrowQuery.GetEscrowTransactionsByTransactionIdsAndStatus([]string{"1", "2"}, model.EscrowStatus_Pending) + expect := fmt.Sprintf("SELECT id, sender_address, recipient_address, approver_address, amount, commission, timeout, status, "+ + "block_height, latest, instruction FROM escrow_transaction WHERE id IN (1, 2) AND status = %d", model.EscrowStatus_Pending) + if query != expect { + t.Errorf("expect: %v\ngot: %v\n", expect, query) + } + }) +} diff --git a/core/service/blockMainService.go b/core/service/blockMainService.go index abc050f60..ab083c7b0 100644 --- a/core/service/blockMainService.go +++ b/core/service/blockMainService.go @@ -611,10 +611,12 @@ func (bs *BlockService) PushBlock(previousBlock, block *model.Block, broadcast, bs.Logger.Error(rollbackErr.Error()) } if broadcast { + // create copy of the block to avoid reference update on block pool + blockToBroadcast := *block // add transactionIDs and remove transaction before broadcast - block.TransactionIDs = transactionIDs - block.Transactions = []*model.Transaction{} - bs.Observer.Notify(observer.BroadcastBlock, block, bs.Chaintype) + blockToBroadcast.TransactionIDs = transactionIDs + blockToBroadcast.Transactions = []*model.Transaction{} + bs.Observer.Notify(observer.BroadcastBlock, blockToBroadcast, bs.Chaintype) } return nil } @@ -664,6 +666,7 @@ func (bs *BlockService) ScanBlockPool() error { ) } err = bs.PushBlock(previousBlock, block, true, true) + if err != nil { bs.Logger.Warnf("ScanBlockPool:PushBlockFail: %v\n", blocker.NewBlocker(blocker.PushMainBlockErr, err.Error(), block, previousBlock)) return blocker.NewBlocker( diff --git a/core/service/transactionCoreService.go b/core/service/transactionCoreService.go index d4c7792f9..ee204305c 100644 --- a/core/service/transactionCoreService.go +++ b/core/service/transactionCoreService.go @@ -2,6 +2,7 @@ package service import ( "database/sql" + "strconv" "github.com/sirupsen/logrus" "github.com/zoobc/zoobc-core/common/blocker" @@ -76,18 +77,54 @@ func (tg *TransactionCoreService) GetTransactionsByIds(transactionIds []int64) ( // GetTransactionsByBlockID get transactions of the block func (tg *TransactionCoreService) GetTransactionsByBlockID(blockID int64) ([]*model.Transaction, error) { - var transactions []*model.Transaction + var ( + transactions []*model.Transaction + escrows []*model.Escrow + txIdsStr []string + err error + ) // get transaction of the block - transactionQ, transactionArg := tg.TransactionQuery.GetTransactionsByBlockID(blockID) - rows, err := tg.QueryExecutor.ExecuteSelect(transactionQ, false, transactionArg...) - + transactions, err = func() ([]*model.Transaction, error) { + transactionQ, transactionArg := tg.TransactionQuery.GetTransactionsByBlockID(blockID) + rows, err := tg.QueryExecutor.ExecuteSelect(transactionQ, false, transactionArg...) + if err != nil { + return nil, blocker.NewBlocker(blocker.DBErr, err.Error()) + } + defer rows.Close() + return tg.TransactionQuery.BuildModel(transactions, rows) + }() if err != nil { return nil, blocker.NewBlocker(blocker.DBErr, err.Error()) } - defer rows.Close() - - return tg.TransactionQuery.BuildModel(transactions, rows) + // fetch escrow if exist + for _, tx := range transactions { + txIdsStr = append(txIdsStr, "'"+strconv.FormatInt(tx.ID, 10)+"'") + } + if len(txIdsStr) > 0 { + escrows, err = func() ([]*model.Escrow, error) { + escrowQ := tg.EscrowTransactionQuery.GetEscrowTransactionsByTransactionIdsAndStatus( + txIdsStr, model.EscrowStatus_Pending, + ) + rows, err := tg.QueryExecutor.ExecuteSelect(escrowQ, false) + if err != nil { + return nil, err + } + defer rows.Close() + return tg.EscrowTransactionQuery.BuildModels(rows) + }() + if err != nil { + return nil, blocker.NewBlocker(blocker.DBErr, err.Error()) + } + for _, escrow := range escrows { + for _, tx := range transactions { + if tx.ID == escrow.ID { + tx.Escrow = escrow + } + } + } + } + return transactions, nil } // ExpiringEscrowTransactions push an observer event that is ExpiringEscrowTransactions, diff --git a/core/service/transactionCoreService_test.go b/core/service/transactionCoreService_test.go index 4eb91d0e5..fbc2c9bdf 100644 --- a/core/service/transactionCoreService_test.go +++ b/core/service/transactionCoreService_test.go @@ -42,6 +42,12 @@ type ( mockGetTransactionsByBlockIDTransactionQueryBuildSuccess struct { query.TransactionQuery } + mockGetTransactionsByBlockIDEscrowTransactionQueryBuildSuccessOne struct { + query.EscrowTransactionQuery + } + mockGetTransactionsByBlockIDEscrowTransactionQueryBuildSuccessEmpty struct { + query.EscrowTransactionQuery + } // GetTransactionsByBlockID mocks ) @@ -57,6 +63,18 @@ var ( TransactionHash: make([]byte, 32), }, } + mockGetTransactionsByBlockIDResultWithEscrow = []*model.Transaction{ + { + TransactionHash: make([]byte, 32), + Escrow: mockGetTransactionByBlockIDEscrowTransactionResultOne[0], + }, + } + mockGetTransactionByBlockIDEscrowTransactionResultOne = []*model.Escrow{ + { + ID: 0, + }, + } + mockGetTransactionByBlockIDEscrowTransactionResultEmpty = make([]*model.Escrow, 0) ) func (*mockGetTransactionsByIdsExecutorFail) ExecuteSelect(query string, tx bool, args ...interface{}) (*sql.Rows, error) { @@ -102,6 +120,16 @@ func (*mockGetTransactionsByBlockIDTransactionQueryBuildFail) BuildModel( return nil, errors.New("mockedError") } +func (*mockGetTransactionsByBlockIDEscrowTransactionQueryBuildSuccessOne) BuildModels( + rows *sql.Rows) ([]*model.Escrow, error) { + return mockGetTransactionByBlockIDEscrowTransactionResultOne, nil +} + +func (*mockGetTransactionsByBlockIDEscrowTransactionQueryBuildSuccessEmpty) BuildModels( + rows *sql.Rows) ([]*model.Escrow, error) { + return mockGetTransactionByBlockIDEscrowTransactionResultEmpty, nil +} + func (*mockGetTransactionsByBlockIDTransactionQueryBuildSuccess) BuildModel( txs []*model.Transaction, rows *sql.Rows) ([]*model.Transaction, error) { return mockGetTransactionsByBlockIDResult, nil @@ -179,8 +207,9 @@ func TestTransactionCoreService_GetTransactionsByIds(t *testing.T) { func TestTransactionCoreService_GetTransactionsByBlockID(t *testing.T) { type fields struct { - TransactionQuery query.TransactionQueryInterface - QueryExecutor query.ExecutorInterface + TransactionQuery query.TransactionQueryInterface + EscrowTransactionQuery query.EscrowTransactionQueryInterface + QueryExecutor query.ExecutorInterface } type args struct { blockID int64 @@ -217,10 +246,24 @@ func TestTransactionCoreService_GetTransactionsByBlockID(t *testing.T) { wantErr: true, }, { - name: "GetTransactionsByBlockID-BuildModel-Success", + name: "GetTransactionsByBlockID-BuildModel-Success-EscrowOneResult", fields: fields{ - TransactionQuery: &mockGetTransactionsByBlockIDTransactionQueryBuildSuccess{}, - QueryExecutor: &mockGetTransactionsByBlockIDExecutorSuccess{}, + TransactionQuery: &mockGetTransactionsByBlockIDTransactionQueryBuildSuccess{}, + EscrowTransactionQuery: &mockGetTransactionsByBlockIDEscrowTransactionQueryBuildSuccessOne{}, + QueryExecutor: &mockGetTransactionsByBlockIDExecutorSuccess{}, + }, + args: args{ + blockID: 1, + }, + want: mockGetTransactionsByBlockIDResultWithEscrow, + wantErr: false, + }, + { + name: "GetTransactionsByBlockID-BuildModel-Success-EscrowEmptyResult", + fields: fields{ + TransactionQuery: &mockGetTransactionsByBlockIDTransactionQueryBuildSuccess{}, + EscrowTransactionQuery: &mockGetTransactionsByBlockIDEscrowTransactionQueryBuildSuccessEmpty{}, + QueryExecutor: &mockGetTransactionsByBlockIDExecutorSuccess{}, }, args: args{ blockID: 1, @@ -232,8 +275,9 @@ func TestTransactionCoreService_GetTransactionsByBlockID(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tg := &TransactionCoreService{ - TransactionQuery: tt.fields.TransactionQuery, - QueryExecutor: tt.fields.QueryExecutor, + TransactionQuery: tt.fields.TransactionQuery, + EscrowTransactionQuery: tt.fields.EscrowTransactionQuery, + QueryExecutor: tt.fields.QueryExecutor, } got, err := tg.GetTransactionsByBlockID(tt.args.blockID) if (err != nil) != tt.wantErr { diff --git a/core/smith/strategy/blocksmithStrategyMain.go b/core/smith/strategy/blocksmithStrategyMain.go index eb0ae31b8..91d416a3c 100644 --- a/core/smith/strategy/blocksmithStrategyMain.go +++ b/core/smith/strategy/blocksmithStrategyMain.go @@ -226,10 +226,10 @@ func (bss *BlocksmithStrategyMain) CanPersistBlock( previousBlock *model.Block, ) error { var ( - err error - ct = &chaintype.MainChain{} - currentTime = time.Now().Unix() - remainder, prevRoundBegin, prevRoundExpired int64 + err error + ct = &chaintype.MainChain{} + currentTime = time.Now().Unix() + remainder, prevRoundBegin, prevRoundExpired, prevRound2Begin, prevRound2Expired int64 ) // always return true for the first block | keeping in mind genesis block's timestamps is far behind, let fork processor // handle to get highest cum-diff block @@ -260,13 +260,20 @@ func (bss *BlocksmithStrategyMain) CanPersistBlock( prevRoundExpired = prevRoundBegin + ct.GetBlocksmithBlockCreationTime() + ct.GetBlocksmithNetworkTolerance() } + if timeRound > 1 { // handle small network, go one more round + prevRound2Start := nearestRoundBeginning - 2*timeForOneRound + prevRound2Begin = prevRound2Start + blocksmithIndex*ct.GetBlocksmithTimeGap() + prevRound2Expired = prevRound2Begin + ct.GetBlocksmithBlockCreationTime() + + ct.GetBlocksmithNetworkTolerance() + } // calculate current round begin and expiry time allowedBeginTime := blocksmithIndex*ct.GetBlocksmithTimeGap() + nearestRoundBeginning expiredTime := allowedBeginTime + ct.GetBlocksmithBlockCreationTime() + ct.GetBlocksmithNetworkTolerance() // check if current time is in {(expire-timeGap) < x < (expire)} in either previous round or current round if (currentTime > (expiredTime-ct.GetBlocksmithTimeGap()) && currentTime <= expiredTime) || - (currentTime > (prevRoundExpired-ct.GetBlocksmithTimeGap()) && currentTime <= prevRoundExpired) { + (currentTime > (prevRoundExpired-ct.GetBlocksmithTimeGap()) && currentTime <= prevRoundExpired) || + (currentTime > (prevRound2Expired-ct.GetBlocksmithTimeGap()) && currentTime <= prevRound2Expired) { return nil } return blocker.NewBlocker(blocker.BlockErr, "CannotPersistBlock")