feat(mempool): add comments and unit tests for mempool, and verify the signature in api module instead of mempool.
This commit is contained in:
parent
eb8baa149a
commit
41141593db
|
@ -5,6 +5,8 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/meshplus/bitxhub-kit/crypto"
|
||||
"github.com/meshplus/bitxhub-kit/crypto/asym"
|
||||
"github.com/meshplus/bitxhub-kit/types"
|
||||
"github.com/meshplus/bitxhub-model/pb"
|
||||
)
|
||||
|
@ -86,12 +88,15 @@ func (cbs *ChainBrokerService) sendTransaction(req *pb.SendTransactionRequest) (
|
|||
To: req.To,
|
||||
Timestamp: req.Timestamp,
|
||||
Data: req.Data,
|
||||
Nonce: uint64(req.Nonce),
|
||||
Nonce: req.Nonce,
|
||||
Signature: req.Signature,
|
||||
Extra: req.Extra,
|
||||
}
|
||||
|
||||
tx.TransactionHash = tx.Hash()
|
||||
ok, _ := asym.Verify(crypto.Secp256k1, tx.Signature, tx.SignHash().Bytes(), tx.From)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid signature")
|
||||
}
|
||||
err := cbs.api.Broker().HandleTransaction(tx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -107,7 +112,7 @@ func (cbs *ChainBrokerService) sendView(req *pb.SendTransactionRequest) (*pb.Rec
|
|||
To: req.To,
|
||||
Timestamp: req.Timestamp,
|
||||
Data: req.Data,
|
||||
Nonce: uint64(req.Nonce),
|
||||
Nonce: req.Nonce,
|
||||
Signature: req.Signature,
|
||||
Extra: req.Extra,
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ solo = false
|
|||
monitor = 40011
|
||||
|
||||
[pprof]
|
||||
enable = false
|
||||
enable = true
|
||||
|
||||
[monitor]
|
||||
enable = true
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/cbergoon/merkletree"
|
||||
"github.com/meshplus/bitxhub-kit/crypto"
|
||||
"github.com/meshplus/bitxhub-kit/crypto/asym"
|
||||
"github.com/meshplus/bitxhub-kit/types"
|
||||
|
@ -19,6 +18,8 @@ import (
|
|||
"github.com/meshplus/bitxhub/pkg/vm"
|
||||
"github.com/meshplus/bitxhub/pkg/vm/boltvm"
|
||||
"github.com/meshplus/bitxhub/pkg/vm/wasm"
|
||||
|
||||
"github.com/cbergoon/merkletree"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ func makeOrderedIndexKey(account string, tx *pb.Transaction) *orderedIndexKey {
|
|||
}
|
||||
}
|
||||
|
||||
func makeSortedNonceKeyKey(nonce uint64) *sortedNonceKey {
|
||||
func makeSortedNonceKey(nonce uint64) *sortedNonceKey {
|
||||
return &sortedNonceKey{
|
||||
nonce: nonce,
|
||||
}
|
||||
|
@ -55,14 +55,14 @@ func newBtreeIndex() *btreeIndex {
|
|||
}
|
||||
}
|
||||
|
||||
func (idx *btreeIndex) insert(tx *pb.Transaction) {
|
||||
idx.data.ReplaceOrInsert(makeSortedNonceKeyKey(tx.Nonce))
|
||||
func (idx *btreeIndex) insertBySortedNonceKey(tx *pb.Transaction) {
|
||||
idx.data.ReplaceOrInsert(makeSortedNonceKey(tx.Nonce))
|
||||
}
|
||||
|
||||
func (idx *btreeIndex) remove(txs map[string][]*pb.Transaction) {
|
||||
func (idx *btreeIndex) removeBySortedNonceKey(txs map[string][]*pb.Transaction) {
|
||||
for _, list := range txs {
|
||||
for _, tx := range list {
|
||||
idx.data.Delete(makeSortedNonceKeyKey(tx.Nonce))
|
||||
idx.data.Delete(makeSortedNonceKey(tx.Nonce))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,51 @@ func TestLess(t *testing.T) {
|
|||
tx.Nonce = 2
|
||||
orderedIndexKey1 := makeOrderedIndexKey("bitxhub", tx)
|
||||
isLess := orderedIndexKey.Less(orderedIndexKey1)
|
||||
ast.Equal(true, isLess)
|
||||
ast.Equal(true, isLess, "orderedIndexKey's account is less than orderedIndexKey1")
|
||||
tx.Nonce = 2
|
||||
orderedIndexKey2 := makeOrderedIndexKey("account", tx)
|
||||
isLess = orderedIndexKey.Less(orderedIndexKey2)
|
||||
ast.Equal(true, isLess, "orderedIndexKey's nonce is less than orderedIndexKey2")
|
||||
}
|
||||
|
||||
func TestSortedNonceKeyLess(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
sortedNonceKey := makeSortedNonceKey(uint64(1))
|
||||
sortedNonceKey1 := makeSortedNonceKey(uint64(2))
|
||||
isLess := sortedNonceKey.Less(sortedNonceKey1)
|
||||
ast.Equal(true, isLess, "sortedNonceKey's nonce is less than sortedNonceKey1")
|
||||
}
|
||||
|
||||
func TestSortedNonceIndex(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
tx := &pb.Transaction{
|
||||
Nonce: uint64(1),
|
||||
}
|
||||
btreeIndex := newBtreeIndex()
|
||||
btreeIndex.insertBySortedNonceKey(tx)
|
||||
ast.Equal(1, btreeIndex.data.Len())
|
||||
|
||||
txn := make(map[string][]*pb.Transaction)
|
||||
list := make([]*pb.Transaction, 1)
|
||||
list[0] = tx
|
||||
txn["account"] = list
|
||||
btreeIndex.removeBySortedNonceKey(txn)
|
||||
ast.Equal(0, btreeIndex.data.Len())
|
||||
}
|
||||
|
||||
func TestOrderedQueueIndex(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
tx := &pb.Transaction{
|
||||
Nonce: uint64(1),
|
||||
}
|
||||
btreeIndex := newBtreeIndex()
|
||||
btreeIndex.insertByOrderedQueueKey("account", tx)
|
||||
ast.Equal(1, btreeIndex.data.Len())
|
||||
|
||||
txn := make(map[string][]*pb.Transaction)
|
||||
list := make([]*pb.Transaction, 1)
|
||||
list[0] = tx
|
||||
txn["account"] = list
|
||||
btreeIndex.removeByOrderedQueueKey(txn)
|
||||
ast.Equal(0, btreeIndex.data.Len())
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@ func (mpi *mempoolImpl) RecvTransaction(tx *pb.Transaction) error {
|
|||
if mpi.txCache.IsFull() && mpi.poolIsFull() {
|
||||
return errors.New("transaction cache and pool are full, we will drop this transaction")
|
||||
}
|
||||
// TODO(YH): how to inform the client that the nonce of is wrong, need to sync to correct nonce.
|
||||
mpi.txCache.recvTxC <- tx
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/meshplus/bitxhub-kit/crypto"
|
||||
"github.com/meshplus/bitxhub-kit/crypto/asym"
|
||||
"github.com/meshplus/bitxhub-kit/types"
|
||||
"github.com/meshplus/bitxhub-model/pb"
|
||||
raftproto "github.com/meshplus/bitxhub/pkg/order/etcdraft/proto"
|
||||
|
@ -48,7 +46,7 @@ func newMempoolImpl(config *Config, storage storage.Storage, batchC chan *raftpr
|
|||
storage: storage,
|
||||
}
|
||||
mpi.txStore = newTransactionStore()
|
||||
mpi.txCache = newTxCache(config.TxSliceTimeout, config.Logger)
|
||||
mpi.txCache = newTxCache(config.TxSliceTimeout, config.TxSliceSize, config.Logger)
|
||||
mpi.subscribe = newSubscribe()
|
||||
if config.BatchSize == 0 {
|
||||
mpi.batchSize = DefaultBatchSize
|
||||
|
@ -156,17 +154,11 @@ func (mpi *mempoolImpl) listenEvent() {
|
|||
func (mpi *mempoolImpl) processTransactions(txs []*pb.Transaction) error {
|
||||
validTxs := make(map[string][]*pb.Transaction)
|
||||
for _, tx := range txs {
|
||||
// check if this tx signature is valid first
|
||||
ok, _ := asym.Verify(crypto.Secp256k1, tx.Signature, tx.SignHash().Bytes(), tx.From)
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid signature")
|
||||
}
|
||||
// check the sequence number of tx
|
||||
// TODO refactor Transaction
|
||||
txAccount, err := getAccount(tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get tx account failed, err: %s", err.Error())
|
||||
mpi.logger.Warningf("get tx account failed, err: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
currentSeqNo := mpi.txStore.nonceCache.getPendingNonce(txAccount)
|
||||
if tx.Nonce < currentSeqNo {
|
||||
|
@ -179,7 +171,7 @@ func (mpi *mempoolImpl) processTransactions(txs []*pb.Transaction) error {
|
|||
mpi.logger.Warningf("Tx %s already received", txHash)
|
||||
continue
|
||||
}
|
||||
_, ok = validTxs[txAccount]
|
||||
_, ok := validTxs[txAccount]
|
||||
if !ok {
|
||||
validTxs[txAccount] = make([]*pb.Transaction, 0)
|
||||
}
|
||||
|
@ -233,7 +225,7 @@ func (txStore *transactionStore) InsertTxs(txs map[string][]*pb.Transaction) map
|
|||
tx: tx,
|
||||
}
|
||||
list.items[tx.Nonce] = txItem
|
||||
list.index.insert(tx)
|
||||
list.index.insertBySortedNonceKey(tx)
|
||||
atomic.AddInt32(&txStore.poolSize, 1)
|
||||
}
|
||||
dirtyAccounts[account] = true
|
||||
|
@ -348,6 +340,7 @@ func (mpi *mempoolImpl) getBlock(ready *raftproto.Ready) *mempoolBatch {
|
|||
return mpi.constructSameBatch(ready)
|
||||
}
|
||||
|
||||
// constructSameBatch only be called by follower, constructs a batch by given ready info.
|
||||
func (mpi *mempoolImpl) constructSameBatch(ready *raftproto.Ready) *mempoolBatch {
|
||||
res := &mempoolBatch{}
|
||||
if txList, ok := mpi.txStore.batchedCache[ready.Height]; ok {
|
||||
|
@ -388,12 +381,11 @@ func (mpi *mempoolImpl) constructSameBatch(ready *raftproto.Ready) *mempoolBatch
|
|||
if len(res.missingTxnHashList) == 0 {
|
||||
// store the batch to cache
|
||||
mpi.txStore.batchedCache[ready.Height] = txList
|
||||
// store the batch to db
|
||||
mpi.batchStore(txList)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// processCommitTransactions removes the transactions in ready.
|
||||
func (mpi *mempoolImpl) processCommitTransactions(ready *raftproto.Ready) {
|
||||
dirtyAccounts := make(map[string]bool)
|
||||
// update current cached commit nonce for account
|
||||
|
@ -418,14 +410,14 @@ func (mpi *mempoolImpl) processCommitTransactions(ready *raftproto.Ready) {
|
|||
for account := range dirtyAccounts {
|
||||
commitNonce := mpi.txStore.nonceCache.getCommitNonce(account)
|
||||
if list, ok := mpi.txStore.allTxs[account]; ok {
|
||||
// remove all previous seq number txs for this account.
|
||||
// removeBySortedNonceKey all previous seq number txs for this account.
|
||||
removedTxs := list.forward(commitNonce)
|
||||
// remove index smaller than commitNonce delete index.
|
||||
// removeBySortedNonceKey index smaller than commitNonce delete index.
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(3)
|
||||
go func(ready map[string][]*pb.Transaction) {
|
||||
defer wg.Done()
|
||||
list.index.remove(removedTxs)
|
||||
list.index.removeBySortedNonceKey(removedTxs)
|
||||
}(removedTxs)
|
||||
go func(ready map[string][]*pb.Transaction) {
|
||||
defer wg.Done()
|
||||
|
@ -440,7 +432,9 @@ func (mpi *mempoolImpl) processCommitTransactions(ready *raftproto.Ready) {
|
|||
atomic.AddInt32(&mpi.txStore.poolSize, -delta)
|
||||
}
|
||||
}
|
||||
mpi.batchDelete(ready.TxHashes)
|
||||
if mpi.isLeader() {
|
||||
mpi.batchDelete(ready.TxHashes)
|
||||
}
|
||||
delete(mpi.txStore.batchedCache, ready.Height)
|
||||
// restart batch timer for remain txs.
|
||||
if mpi.isLeader() {
|
||||
|
@ -528,21 +522,23 @@ func (mpi *mempoolImpl) loadTxnFromStorage(fetchTxnRequest *FetchTxnRequest) (ma
|
|||
txList := make(map[uint64]*pb.Transaction)
|
||||
for index, txHash := range missingHashList {
|
||||
var (
|
||||
tx *pb.Transaction
|
||||
rawHash types.Hash
|
||||
err error
|
||||
ok bool
|
||||
)
|
||||
if rawHash, err = hex2Hash(txHash); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tx, ok := mpi.load(rawHash); !ok {
|
||||
if tx, ok = mpi.load(rawHash); !ok {
|
||||
return nil, errors.New("can't load tx from storage")
|
||||
} else {
|
||||
txList[index] = tx
|
||||
}
|
||||
txList[index] = tx
|
||||
}
|
||||
return txList, nil
|
||||
}
|
||||
|
||||
// loadTxnFromLedger find missing transactions from ledger.
|
||||
func (mpi *mempoolImpl) loadTxnFromLedger(fetchTxnRequest *FetchTxnRequest) (map[uint64]*pb.Transaction, error) {
|
||||
missingHashList := fetchTxnRequest.MissingTxHashes
|
||||
txList := make(map[uint64]*pb.Transaction)
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
package mempool
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/meshplus/bitxhub-model/pb"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestProcessTransactions(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
mpi, batchC := mockMempoolImpl()
|
||||
defer cleanTestData()
|
||||
|
||||
txList := make([]*pb.Transaction, 0)
|
||||
privKey1 := genPrivKey()
|
||||
account1, _ := privKey1.PublicKey().Address()
|
||||
privKey2 := genPrivKey()
|
||||
account2, _ := privKey2.PublicKey().Address()
|
||||
tx1 := constructTx(uint64(1), &privKey1)
|
||||
tx2 := constructTx(uint64(2), &privKey1)
|
||||
tx3 := constructTx(uint64(1), &privKey2)
|
||||
tx4 := constructTx(uint64(2), &privKey2)
|
||||
tx5 := constructTx(uint64(4), &privKey2)
|
||||
txList = append(txList, tx1, tx2, tx3, tx4, tx5)
|
||||
err := mpi.processTransactions(txList)
|
||||
ast.Nil(err)
|
||||
ast.Equal(4, mpi.txStore.priorityIndex.size())
|
||||
ast.Equal(1, mpi.txStore.parkingLotIndex.size())
|
||||
ast.Equal(5, len(mpi.txStore.txHashMap))
|
||||
ast.Equal(0, len(mpi.txStore.batchedCache))
|
||||
ast.Equal(2, mpi.txStore.allTxs[account1.Hex()].index.size())
|
||||
ast.Equal(3, mpi.txStore.allTxs[account2.Hex()].index.size())
|
||||
ast.Equal(uint64(1), mpi.txStore.nonceCache.getCommitNonce(account1.Hex()))
|
||||
ast.Equal(uint64(3), mpi.txStore.nonceCache.getPendingNonce(account1.Hex()))
|
||||
ast.Equal(uint64(1), mpi.txStore.nonceCache.getCommitNonce(account2.Hex()))
|
||||
ast.Equal(uint64(3), mpi.txStore.nonceCache.getPendingNonce(account2.Hex()))
|
||||
|
||||
go func() {
|
||||
mpi.batchSize = 4
|
||||
mpi.leader = mpi.localID
|
||||
tx6 := constructTx(uint64(3), &privKey1)
|
||||
tx7 := constructTx(uint64(5), &privKey2)
|
||||
txList = make([]*pb.Transaction, 0)
|
||||
txList = append(txList, tx6, tx7)
|
||||
err = mpi.processTransactions(txList)
|
||||
ast.Nil(err)
|
||||
}()
|
||||
select {
|
||||
case batch := <-batchC:
|
||||
ast.Equal(4, len(batch.TxHashes))
|
||||
ast.Equal(uint64(2), batch.Height)
|
||||
ast.Equal(uint64(1), mpi.txStore.priorityNonBatchSize)
|
||||
ast.Equal(5, mpi.txStore.priorityIndex.size())
|
||||
ast.Equal(2, mpi.txStore.parkingLotIndex.size())
|
||||
ast.Equal(7, len(mpi.txStore.txHashMap))
|
||||
ast.Equal(1, len(mpi.txStore.batchedCache))
|
||||
ast.Equal(4, len(mpi.txStore.batchedCache[uint64(2)]))
|
||||
ast.Equal(3, mpi.txStore.allTxs[account1.Hex()].index.size())
|
||||
ast.Equal(4, mpi.txStore.allTxs[account2.Hex()].index.size())
|
||||
ast.Equal(uint64(4), mpi.txStore.nonceCache.getPendingNonce(account1.Hex()))
|
||||
ast.Equal(uint64(3), mpi.txStore.nonceCache.getPendingNonce(account2.Hex()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessFetchTxnRequest(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
mpi, _ := mockMempoolImpl()
|
||||
defer cleanTestData()
|
||||
|
||||
privKey1 := genPrivKey()
|
||||
tx1 := constructTx(uint64(1), &privKey1)
|
||||
var txList []*pb.Transaction
|
||||
txList = append(txList, tx1)
|
||||
|
||||
missingList := make(map[uint64]string)
|
||||
missingList[0] = tx1.TransactionHash.Hex()
|
||||
fetchTxnRequest := &FetchTxnRequest{
|
||||
Height: uint64(2),
|
||||
MissingTxHashes: missingList,
|
||||
}
|
||||
|
||||
err := mpi.processFetchTxnRequest(fetchTxnRequest)
|
||||
ast.NotNil(err, " can't find the missing tx from local")
|
||||
|
||||
// load tx from cache
|
||||
err = mpi.processTransactions(txList)
|
||||
mpi.txStore.batchedCache[uint64(2)] = txList
|
||||
err = mpi.processFetchTxnRequest(fetchTxnRequest)
|
||||
ast.Nil(err)
|
||||
}
|
||||
|
||||
func TestProcessFetchTxnResponse(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
mpi, _ := mockMempoolImpl()
|
||||
defer cleanTestData()
|
||||
|
||||
fetchTxnResponse := &FetchTxnResponse{
|
||||
Height: uint64(2),
|
||||
}
|
||||
err := mpi.processFetchTxnResponse(fetchTxnResponse)
|
||||
ast.NotNil(err, "can't find batch 2 from missingBatch")
|
||||
|
||||
privKey1 := genPrivKey()
|
||||
tx1 := constructTx(uint64(1), &privKey1)
|
||||
missingList := make(map[uint64]string)
|
||||
missingList[0] = tx1.TransactionHash.Hex()
|
||||
mpi.txStore.missingBatch[uint64(2)] = missingList
|
||||
|
||||
fetchTxnResponse.MissingTxnList = make(map[uint64]*pb.Transaction)
|
||||
fetchTxnResponse.MissingTxnList[0] = tx1
|
||||
fetchTxnResponse.MissingTxnList[1] = tx1
|
||||
err = mpi.processFetchTxnResponse(fetchTxnResponse)
|
||||
ast.NotNil(err, "length mismatch")
|
||||
|
||||
delete(fetchTxnResponse.MissingTxnList, 1)
|
||||
err = mpi.processFetchTxnResponse(fetchTxnResponse)
|
||||
ast.Nil(err)
|
||||
ast.Equal(0, len(mpi.txStore.missingBatch))
|
||||
}
|
|
@ -1,264 +1,233 @@
|
|||
package mempool
|
||||
//
|
||||
//import (
|
||||
// "encoding/json"
|
||||
// "fmt"
|
||||
// "math/rand"
|
||||
// "sort"
|
||||
// "testing"
|
||||
// "time"
|
||||
//
|
||||
// "github.com/google/btree"
|
||||
// "github.com/meshplus/bitxhub-kit/crypto"
|
||||
// "github.com/meshplus/bitxhub-kit/crypto/asym"
|
||||
// "github.com/meshplus/bitxhub-kit/types"
|
||||
// "github.com/meshplus/bitxhub-model/pb"
|
||||
// "github.com/stretchr/testify/require"
|
||||
//)
|
||||
//
|
||||
//var (
|
||||
// InterchainContractAddr = types.String2Address("000000000000000000000000000000000000000a")
|
||||
// appchains = []string{
|
||||
// "0x3f9d18f7c3a6e5e4c0b877fe3e688ab08840b997",
|
||||
// "0xa8ae1bbc1105944a84a71b89056930d951d420fe",
|
||||
// "0x929545f44692178edb7fa468b44c5351596184ba",
|
||||
// "0x7368022e6659236983eb959b8a1fa22577d48294",
|
||||
// }
|
||||
//)
|
||||
//
|
||||
//func TestCoreMemPool_RecvTransactions(t *testing.T) {
|
||||
// readyTxs := make([]*pb.Transaction, 0)
|
||||
// nonreadyTxs := make([]*pb.Transaction, 0)
|
||||
// txIndex := make(map[string]*pb.Transaction)
|
||||
// privKey, err := asym.GenerateKeyPair(crypto.Secp256k1)
|
||||
// require.Nil(t, err)
|
||||
// pubKey := privKey.PublicKey()
|
||||
// addr, err := pubKey.Address()
|
||||
// require.Nil(t, err)
|
||||
//
|
||||
// sort.Strings(appchains)
|
||||
// readyTxsLen := 2
|
||||
// for _, appchain := range appchains {
|
||||
// for i := 1; i <= readyTxsLen; i++ {
|
||||
// readyTxs = append(readyTxs, mockTxhelper(t, txIndex, appchain, uint64(i)))
|
||||
// // add unready txs
|
||||
// nonreadyTxs = append(nonreadyTxs, mockTxhelper(t, txIndex, appchain, uint64(i+readyTxsLen+1)))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // set timestamp and signature for txs
|
||||
// for _, tx := range readyTxs {
|
||||
// addSigAndTime(t, tx, addr, privKey)
|
||||
// }
|
||||
// for _, tx := range nonreadyTxs {
|
||||
// addSigAndTime(t, tx, addr, privKey)
|
||||
// }
|
||||
//
|
||||
// // shuffle tx order
|
||||
// rand.Seed(time.Now().UnixNano())
|
||||
// rand.Shuffle(len(readyTxs), func(i, j int) {
|
||||
// readyTxs[i], readyTxs[j] = readyTxs[j], readyTxs[i]
|
||||
// })
|
||||
// memPool := newMempoolImpl(nil, nil, nil)
|
||||
// require.Nil(t, memPool.recvTransactions(readyTxs))
|
||||
// require.Nil(t, memPool.RecvTransactions(nonreadyTxs))
|
||||
//
|
||||
// // check if all txs are indexed in memPool.allTxs
|
||||
// // and if all txs are indexed by its account and nonce
|
||||
// require.Equal(t, len(appchains), len(memPool.transactionStore.allTxs))
|
||||
// checkAllTxs(t, memPool, txIndex, readyTxsLen, readyTxsLen)
|
||||
// checkHashMap(t, memPool, true, readyTxs, nonreadyTxs)
|
||||
//
|
||||
// // check if priorityIndex is correctly recorded
|
||||
// require.Equal(t, len(readyTxs), memPool.transactionStore.priorityIndex.data.Len())
|
||||
// for _, tx := range readyTxs {
|
||||
// ok := memPool.priorityIndex.data.Has(makeKey(tx))
|
||||
// require.True(t, ok)
|
||||
// ok = memPool.transactionStore.parkingLotIndex.data.Has(makeKey(tx))
|
||||
// require.True(t, !ok)
|
||||
// }
|
||||
//
|
||||
// // check if parkingLotIndex is correctly recorded
|
||||
// require.Equal(t, len(nonreadyTxs), memPool.transactionStore.parkingLotIndex.data.Len())
|
||||
// for _, tx := range nonreadyTxs {
|
||||
// ok := memPool.transactionStore.parkingLotIndex.data.Has(makeKey(tx))
|
||||
// require.True(t, ok)
|
||||
// ok = memPool.transactionStore.priorityIndex.data.Has(makeKey(tx))
|
||||
// require.True(t, !ok)
|
||||
// }
|
||||
//
|
||||
// // add the missing tx for each appchain
|
||||
// missingTxs := make([]*pb.Transaction, 0, len(appchains))
|
||||
// for _, appchain := range appchains {
|
||||
// missingTxs = append(missingTxs, mockTxhelper(t, txIndex, appchain, uint64(readyTxsLen+1)))
|
||||
// }
|
||||
// for _, tx := range missingTxs {
|
||||
// addSigAndTime(t, tx, addr, privKey)
|
||||
// }
|
||||
//
|
||||
// require.Nil(t, memPool.RecvTransactions(missingTxs))
|
||||
//
|
||||
// // check if parkingLotIndex is empty now
|
||||
// require.Equal(t, 0, memPool.transactionStore.parkingLotIndex.data.Len())
|
||||
// // check if priorityIndex has received missingTxs and txs from original parkingLotIndex
|
||||
// for _, tx := range missingTxs {
|
||||
// ok := memPool.transactionStore.priorityIndex.data.Has(makeKey(tx))
|
||||
// require.True(t, ok)
|
||||
// }
|
||||
// for _, tx := range nonreadyTxs {
|
||||
// ok := memPool.transactionStore.priorityIndex.data.Has(makeKey(tx))
|
||||
// require.True(t, ok)
|
||||
// }
|
||||
// checkHashMap(t, memPool, true, readyTxs, nonreadyTxs, missingTxs)
|
||||
//}
|
||||
//
|
||||
//func TestCoreMemPool_RecvTransactions_Margin(t *testing.T) {
|
||||
// readyTxs := make([]*pb.Transaction, 0)
|
||||
// identicalNonceTxs := make([]*pb.Transaction, 0)
|
||||
// replayedTxs := make([]*pb.Transaction, 0)
|
||||
// readyTxIndex := make(map[string]*pb.Transaction)
|
||||
// identicalNonceTxIndex := make(map[string]*pb.Transaction)
|
||||
// privKey, err := asym.GenerateKeyPair(crypto.Secp256k1)
|
||||
// require.Nil(t, err)
|
||||
// pubKey := privKey.PublicKey()
|
||||
// addr, err := pubKey.Address()
|
||||
// require.Nil(t, err)
|
||||
//
|
||||
// sort.Strings(appchains)
|
||||
// readyTxsLen := 2
|
||||
// for _, appchain := range appchains {
|
||||
// for i := 1; i <= readyTxsLen; i++ {
|
||||
// tx := mockTxhelper(t, readyTxIndex, appchain, uint64(i))
|
||||
// readyTxs = append(readyTxs, tx)
|
||||
// // add tx with same index but different content
|
||||
// identicalNonceTx := mockTxhelper(t, identicalNonceTxIndex, appchain, uint64(i))
|
||||
// identicalNonceTxs = append(identicalNonceTxs, identicalNonceTx)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // set timestamp and signature for txs
|
||||
// for _, tx := range readyTxs {
|
||||
// addSigAndTime(t, tx, addr, privKey)
|
||||
// // add repeated txs
|
||||
// replayedTxs = append(replayedTxs, tx)
|
||||
// }
|
||||
// for _, tx := range identicalNonceTxs {
|
||||
// addSigAndTime(t, tx, addr, privKey)
|
||||
// }
|
||||
//
|
||||
// memPool := New()
|
||||
// require.Nil(t, memPool.RecvTransactions(readyTxs))
|
||||
// require.NotNil(t, memPool.RecvTransactions(replayedTxs))
|
||||
// err = memPool.RecvTransactions(identicalNonceTxs)
|
||||
// require.NotNil(t, err)
|
||||
//
|
||||
// require.Equal(t, len(appchains), len(memPool.transactionStore.allTxs))
|
||||
// checkAllTxs(t, memPool, readyTxIndex, readyTxsLen, 0)
|
||||
// checkHashMap(t, memPool, true, readyTxs)
|
||||
// checkHashMap(t, memPool, false, identicalNonceTxs)
|
||||
//}
|
||||
//
|
||||
//func checkAllTxs(t *testing.T, memPool *CoreMemPool,
|
||||
// txIndex map[string]*pb.Transaction, readyTxsLen, nonReadyTxLen int) {
|
||||
// for _, appchain := range appchains {
|
||||
// idx := uint64(1)
|
||||
// accountAddr := fmt.Sprintf("%s-%s", appchain, appchain)
|
||||
//
|
||||
// txMap, ok := memPool.transactionStore.allTxs[accountAddr]
|
||||
// require.True(t, ok)
|
||||
// require.NotNil(t, txMap.index)
|
||||
// require.Equal(t, readyTxsLen+nonReadyTxLen, txMap.index.data.Len())
|
||||
// require.Equal(t, readyTxsLen+nonReadyTxLen, len(txMap.items))
|
||||
// txMap.index.data.Ascend(func(i btree.Item) bool {
|
||||
// orderedKey := i.(*orderedIndexKey)
|
||||
// if idx <= uint64(readyTxsLen) {
|
||||
// require.Equal(t, orderedKey.nonce, idx)
|
||||
// } else {
|
||||
// require.Equal(t, orderedKey.nonce, idx+1)
|
||||
// }
|
||||
// require.Equal(t, orderedKey.accountAddress, accountAddr)
|
||||
//
|
||||
// ibtpID := fmt.Sprintf("%s-%s-%d", appchain, appchain, orderedKey.nonce)
|
||||
// require.Equal(t, txIndex[ibtpID], txMap.items[orderedKey.nonce])
|
||||
// idx++
|
||||
// return true
|
||||
// })
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func checkHashMap(t *testing.T, memPool *CoreMemPool, expectedStatus bool, txsSlice ...[]*pb.Transaction) {
|
||||
// for _, txs := range txsSlice {
|
||||
// for _, tx := range txs {
|
||||
// _, ok := memPool.transactionStore.txHashMap[tx.TransactionHash.Hex()]
|
||||
// require.Equal(t, expectedStatus, ok)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func mockTxhelper(t *testing.T, txIndex map[string]*pb.Transaction, appchainAddr string, index uint64) *pb.Transaction {
|
||||
// ibtp := mockIBTP(t, appchainAddr, appchainAddr, index)
|
||||
// tx := mockInterchainTx(t, ibtp)
|
||||
// txIndex[ibtp.ID()] = tx
|
||||
// return tx
|
||||
//}
|
||||
//
|
||||
//func addSigAndTime(t *testing.T, tx *pb.Transaction, addr types.Address, privKey crypto.PrivateKey) {
|
||||
// tx.Timestamp = time.Now().UnixNano()
|
||||
// tx.From = addr
|
||||
// sig, err := privKey.Sign(tx.SignHash().Bytes())
|
||||
// tx.Signature = sig
|
||||
// require.Nil(t, err)
|
||||
// tx.TransactionHash = tx.Hash()
|
||||
//}
|
||||
//
|
||||
//func mockInterchainTx(t *testing.T, ibtp *pb.IBTP) *pb.Transaction {
|
||||
// ib, err := ibtp.Marshal()
|
||||
// require.Nil(t, err)
|
||||
//
|
||||
// ipd := &pb.InvokePayload{
|
||||
// Method: "HandleIBTP",
|
||||
// Args: []*pb.Arg{{Value: ib}},
|
||||
// }
|
||||
// pd, err := ipd.Marshal()
|
||||
// require.Nil(t, err)
|
||||
//
|
||||
// data := &pb.TransactionData{
|
||||
// VmType: pb.TransactionData_BVM,
|
||||
// Type: pb.TransactionData_INVOKE,
|
||||
// Payload: pd,
|
||||
// }
|
||||
//
|
||||
// return &pb.Transaction{
|
||||
// To: InterchainContractAddr,
|
||||
// Nonce: int64(ibtp.Index),
|
||||
// Data: data,
|
||||
// Extra: []byte(fmt.Sprintf("%s-%s", ibtp.From, ibtp.To)),
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func mockIBTP(t *testing.T, from, to string, nonce uint64) *pb.IBTP {
|
||||
// content := pb.Content{
|
||||
// SrcContractId: from,
|
||||
// DstContractId: from,
|
||||
// Func: "interchainget",
|
||||
// Args: [][]byte{[]byte("Alice"), []byte("10")},
|
||||
// }
|
||||
//
|
||||
// bytes, err := content.Marshal()
|
||||
// require.Nil(t, err)
|
||||
//
|
||||
// ibtppd, err := json.Marshal(pb.Payload{
|
||||
// Encrypted: false,
|
||||
// Content: bytes,
|
||||
// })
|
||||
// require.Nil(t, err)
|
||||
//
|
||||
// return &pb.IBTP{
|
||||
// From: from,
|
||||
// To: to,
|
||||
// Payload: ibtppd,
|
||||
// Index: nonce,
|
||||
// Type: pb.IBTP_INTERCHAIN,
|
||||
// Timestamp: time.Now().UnixNano(),
|
||||
// }
|
||||
//}
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/meshplus/bitxhub-kit/types"
|
||||
"github.com/meshplus/bitxhub-model/pb"
|
||||
raftproto "github.com/meshplus/bitxhub/pkg/order/etcdraft/proto"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRecvTransaction(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
mempool, _ := mockMempoolImpl()
|
||||
defer cleanTestData()
|
||||
|
||||
privKey1 := genPrivKey()
|
||||
tx1 := constructTx(uint64(1), &privKey1)
|
||||
go mempool.txCache.listenEvent()
|
||||
go func() {
|
||||
_ = mempool.RecvTransaction(tx1)
|
||||
}()
|
||||
select {
|
||||
case txSet := <-mempool.txCache.txSetC:
|
||||
ast.Equal(1, len(txSet.TxList))
|
||||
}
|
||||
|
||||
err := mempool.Start()
|
||||
ast.Nil(err)
|
||||
privKey2 := genPrivKey()
|
||||
go func() {
|
||||
_ = mempool.RecvTransaction(tx1)
|
||||
}()
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
ast.Equal(1, mempool.txStore.priorityIndex.size())
|
||||
ast.Equal(0, mempool.txStore.parkingLotIndex.size())
|
||||
|
||||
tx2 := constructTx(uint64(2), &privKey1)
|
||||
tx3 := constructTx(uint64(1), &privKey2)
|
||||
tx4 := constructTx(uint64(2), &privKey2)
|
||||
go func() {
|
||||
_ = mempool.RecvTransaction(tx4)
|
||||
}()
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
ast.Equal(1, mempool.txStore.priorityIndex.size())
|
||||
ast.Equal(1, mempool.txStore.parkingLotIndex.size())
|
||||
go func() {
|
||||
_ = mempool.RecvTransaction(tx2)
|
||||
}()
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
ast.Equal(2, mempool.txStore.priorityIndex.size())
|
||||
ast.Equal(1, mempool.txStore.parkingLotIndex.size())
|
||||
go func() {
|
||||
_ = mempool.RecvTransaction(tx3)
|
||||
}()
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
ast.Equal(4, mempool.txStore.priorityIndex.size())
|
||||
ast.Equal(1, mempool.txStore.parkingLotIndex.size(), "delete tx4 until finishing executor")
|
||||
mempool.Stop()
|
||||
}
|
||||
|
||||
func TestRecvForwardTxs(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
mempool, _ := mockMempoolImpl()
|
||||
defer cleanTestData()
|
||||
|
||||
privKey1 := genPrivKey()
|
||||
tx := constructTx(uint64(1), &privKey1)
|
||||
txList := []*pb.Transaction{tx}
|
||||
txSlice := &TxSlice{TxList: txList}
|
||||
go mempool.RecvForwardTxs(txSlice)
|
||||
select {
|
||||
case txSet := <-mempool.subscribe.txForwardC:
|
||||
ast.Equal(1, len(txSet.TxList))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateLeader(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
mempool, _ := mockMempoolImpl()
|
||||
mempool.Start()
|
||||
defer cleanTestData()
|
||||
go mempool.UpdateLeader(uint64(2))
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
ast.Equal(uint64(2), mempool.leader)
|
||||
}
|
||||
|
||||
func TestGetBlock(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
mempool, _ := mockMempoolImpl()
|
||||
err := mempool.Start()
|
||||
ast.Nil(err)
|
||||
defer cleanTestData()
|
||||
|
||||
privKey1 := genPrivKey()
|
||||
privKey2 := genPrivKey()
|
||||
tx1 := constructTx(uint64(1), &privKey1)
|
||||
tx2 := constructTx(uint64(2), &privKey1)
|
||||
tx3 := constructTx(uint64(2), &privKey2)
|
||||
tx4 := constructTx(uint64(4), &privKey2)
|
||||
tx5 := constructTx(uint64(1), &privKey2)
|
||||
var txList []*pb.Transaction
|
||||
var txHashList []types.Hash
|
||||
txList = append(txList, tx1, tx2, tx3, tx4)
|
||||
txHashList = append(txHashList, tx1.TransactionHash, tx2.TransactionHash, tx3.TransactionHash, tx5.TransactionHash)
|
||||
err = mempool.processTransactions(txList)
|
||||
ast.Nil(err)
|
||||
ready := &raftproto.Ready{
|
||||
Height: uint64(2),
|
||||
TxHashes: txHashList,
|
||||
}
|
||||
missingTxnHashList, txList := mempool.GetBlock(ready)
|
||||
ast.Equal(1, len(missingTxnHashList), "missing tx5")
|
||||
ast.Equal(3, len(txList))
|
||||
|
||||
txList = []*pb.Transaction{}
|
||||
txList = append(txList, tx5)
|
||||
err = mempool.processTransactions(txList)
|
||||
missingTxnHashList, txList = mempool.GetBlock(ready)
|
||||
ast.Equal(0, len(missingTxnHashList))
|
||||
ast.Equal(4, len(txList))
|
||||
}
|
||||
|
||||
func TestGetPendingNonceByAccount(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
mpi, _ := mockMempoolImpl()
|
||||
err := mpi.Start()
|
||||
ast.Nil(err)
|
||||
defer cleanTestData()
|
||||
|
||||
privKey1 := genPrivKey()
|
||||
account1, _ := privKey1.PublicKey().Address()
|
||||
nonce := mpi.GetPendingNonceByAccount(account1.Hex())
|
||||
ast.Equal(uint64(1), nonce)
|
||||
|
||||
privKey2 := genPrivKey()
|
||||
account2, _ := privKey2.PublicKey().Address()
|
||||
tx1 := constructTx(uint64(1), &privKey1)
|
||||
tx2 := constructTx(uint64(2), &privKey1)
|
||||
tx3 := constructTx(uint64(1), &privKey2)
|
||||
tx4 := constructTx(uint64(2), &privKey2)
|
||||
tx5 := constructTx(uint64(4), &privKey2)
|
||||
var txList []*pb.Transaction
|
||||
txList = append(txList, tx1, tx2, tx3, tx4, tx5)
|
||||
err = mpi.processTransactions(txList)
|
||||
ast.Nil(err)
|
||||
nonce = mpi.GetPendingNonceByAccount(account1.Hex())
|
||||
ast.Equal(uint64(3), nonce)
|
||||
nonce = mpi.GetPendingNonceByAccount(account2.Hex())
|
||||
ast.Equal(uint64(3), nonce, "not 4")
|
||||
}
|
||||
|
||||
func TestCommitTransactions(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
mpi, _ := mockMempoolImpl()
|
||||
err := mpi.Start()
|
||||
ast.Nil(err)
|
||||
defer cleanTestData()
|
||||
|
||||
privKey1 := genPrivKey()
|
||||
account1, _ := privKey1.PublicKey().Address()
|
||||
nonce := mpi.GetPendingNonceByAccount(account1.Hex())
|
||||
ast.Equal(uint64(1), nonce)
|
||||
|
||||
privKey2 := genPrivKey()
|
||||
tx1 := constructTx(uint64(1), &privKey1)
|
||||
tx2 := constructTx(uint64(2), &privKey1)
|
||||
tx3 := constructTx(uint64(1), &privKey2)
|
||||
tx4 := constructTx(uint64(4), &privKey2)
|
||||
var txList []*pb.Transaction
|
||||
txList = append(txList, tx1, tx2, tx3, tx4)
|
||||
mpi.leader = uint64(1)
|
||||
err = mpi.processTransactions(txList)
|
||||
ast.Equal(3, mpi.txStore.priorityIndex.size())
|
||||
ast.Equal(1, mpi.txStore.parkingLotIndex.size())
|
||||
ast.Equal(0, len(mpi.txStore.batchedCache))
|
||||
|
||||
go func() {
|
||||
<-mpi.batchC
|
||||
}()
|
||||
tx5 := constructTx(uint64(2), &privKey2)
|
||||
txList = []*pb.Transaction{}
|
||||
txList = append(txList, tx5)
|
||||
err = mpi.processTransactions(txList)
|
||||
ast.Equal(4, mpi.txStore.priorityIndex.size())
|
||||
ast.Equal(1, mpi.txStore.parkingLotIndex.size())
|
||||
ast.Equal(1, len(mpi.txStore.batchedCache))
|
||||
height := mpi.GetChainHeight()
|
||||
ast.Equal(uint64(2), height)
|
||||
|
||||
var txHashList []types.Hash
|
||||
txHashList = append(txHashList, tx1.TransactionHash, tx2.TransactionHash, tx3.TransactionHash, tx5.TransactionHash)
|
||||
ready := &raftproto.Ready{
|
||||
Height: uint64(2),
|
||||
TxHashes: txHashList,
|
||||
}
|
||||
mpi.CommitTransactions(ready)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
ast.Equal(0, mpi.txStore.priorityIndex.size())
|
||||
ast.Equal(1, mpi.txStore.parkingLotIndex.size())
|
||||
ast.Equal(0, len(mpi.txStore.batchedCache))
|
||||
}
|
||||
|
||||
func TestFetchTxn(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
mpi, _ := mockMempoolImpl()
|
||||
err := mpi.Start()
|
||||
ast.Nil(err)
|
||||
defer cleanTestData()
|
||||
|
||||
missingList := make(map[uint64]string)
|
||||
missingList[0] = "tx1"
|
||||
lostTxnEvent := &LocalMissingTxnEvent{
|
||||
Height: uint64(2),
|
||||
MissingTxnHashList: missingList,
|
||||
WaitC: make(chan bool),
|
||||
}
|
||||
mpi.FetchTxn(lostTxnEvent)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
ast.Equal(1, len(mpi.txStore.missingBatch))
|
||||
}
|
||||
|
||||
func TestIncreaseChainHeight(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
mpi, _ := mockMempoolImpl()
|
||||
defer cleanTestData()
|
||||
|
||||
ast.Equal(uint64(1), mpi.GetChainHeight())
|
||||
mpi.increaseBatchSeqNo()
|
||||
ast.Equal(uint64(2), mpi.GetChainHeight())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
package mempool
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/meshplus/bitxhub-kit/crypto"
|
||||
"github.com/meshplus/bitxhub-kit/crypto/asym"
|
||||
"github.com/meshplus/bitxhub-kit/log"
|
||||
"github.com/meshplus/bitxhub-kit/types"
|
||||
"github.com/meshplus/bitxhub-model/pb"
|
||||
"github.com/meshplus/bitxhub/internal/model/events"
|
||||
raftproto "github.com/meshplus/bitxhub/pkg/order/etcdraft/proto"
|
||||
"github.com/meshplus/bitxhub/pkg/storage/leveldb"
|
||||
network "github.com/meshplus/go-lightp2p"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
var (
|
||||
InterchainContractAddr = types.String2Address("000000000000000000000000000000000000000a")
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultTestChainHeight = uint64(1)
|
||||
DefaultTestBatchSize = uint64(4)
|
||||
DefaultTestTxSetSize = uint64(1)
|
||||
LevelDBDir = "test-db"
|
||||
)
|
||||
|
||||
func mockMempoolImpl() (*mempoolImpl, chan *raftproto.Ready) {
|
||||
config := &Config{
|
||||
ID: 1,
|
||||
ChainHeight: DefaultTestChainHeight,
|
||||
BatchSize: DefaultTestBatchSize,
|
||||
PoolSize: DefaultPoolSize,
|
||||
TxSliceSize: DefaultTestTxSetSize,
|
||||
BatchTick: DefaultBatchTick,
|
||||
FetchTimeout: DefaultFetchTxnTimeout,
|
||||
TxSliceTimeout: DefaultTxSetTick,
|
||||
Logger: log.NewWithModule("consensus"),
|
||||
}
|
||||
config.PeerMgr = newMockPeerMgr()
|
||||
db, _ := leveldb.New(LevelDBDir)
|
||||
proposalC := make(chan *raftproto.Ready)
|
||||
mempool := newMempoolImpl(config, db, proposalC)
|
||||
return mempool, proposalC
|
||||
}
|
||||
|
||||
func genPrivKey() crypto.PrivateKey {
|
||||
privKey, _ := asym.GenerateKeyPair(crypto.Secp256k1)
|
||||
return privKey
|
||||
}
|
||||
|
||||
func constructTx(nonce uint64, privKey *crypto.PrivateKey) *pb.Transaction {
|
||||
var privK crypto.PrivateKey
|
||||
if privKey == nil {
|
||||
privK = genPrivKey()
|
||||
}
|
||||
privK = *privKey
|
||||
pubKey := privK.PublicKey()
|
||||
addr, _ := pubKey.Address()
|
||||
tx := &pb.Transaction{Nonce: nonce}
|
||||
tx.Timestamp = time.Now().UnixNano()
|
||||
tx.From = addr
|
||||
sig, _ := privK.Sign(tx.SignHash().Bytes())
|
||||
tx.Signature = sig
|
||||
tx.TransactionHash = tx.Hash()
|
||||
return tx
|
||||
}
|
||||
|
||||
func constructIBTPTx(nonce uint64, privKey *crypto.PrivateKey) *pb.Transaction {
|
||||
var privK crypto.PrivateKey
|
||||
if privKey == nil {
|
||||
privK = genPrivKey()
|
||||
}
|
||||
privK = *privKey
|
||||
pubKey := privK.PublicKey()
|
||||
from, _ := pubKey.Address()
|
||||
to := from.Hex()
|
||||
ibtp := mockIBTP(from.Hex(), to, nonce)
|
||||
tx := mockInterChainTx(ibtp)
|
||||
tx.Timestamp = time.Now().UnixNano()
|
||||
sig, _ := privK.Sign(tx.SignHash().Bytes())
|
||||
tx.Signature = sig
|
||||
tx.TransactionHash = tx.Hash()
|
||||
return tx
|
||||
}
|
||||
|
||||
func cleanTestData() bool {
|
||||
err := os.RemoveAll(LevelDBDir)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func mockInterChainTx(ibtp *pb.IBTP) *pb.Transaction {
|
||||
ib, _ := ibtp.Marshal()
|
||||
ipd := &pb.InvokePayload{
|
||||
Method: "HandleIBTP",
|
||||
Args: []*pb.Arg{{Value: ib}},
|
||||
}
|
||||
pd, _ := ipd.Marshal()
|
||||
data := &pb.TransactionData{
|
||||
VmType: pb.TransactionData_BVM,
|
||||
Type: pb.TransactionData_INVOKE,
|
||||
Payload: pd,
|
||||
}
|
||||
return &pb.Transaction{
|
||||
To: InterchainContractAddr,
|
||||
Nonce: ibtp.Index,
|
||||
Data: data,
|
||||
Extra: []byte(fmt.Sprintf("%s-%s-%d", ibtp.From, ibtp.To, ibtp.Type)),
|
||||
}
|
||||
}
|
||||
|
||||
func mockIBTP(from, to string, nonce uint64) *pb.IBTP {
|
||||
content := pb.Content{
|
||||
SrcContractId: from,
|
||||
DstContractId: from,
|
||||
Func: "interchainget",
|
||||
Args: [][]byte{[]byte("Alice"), []byte("10")},
|
||||
}
|
||||
bytes, _ := content.Marshal()
|
||||
ibtppd, _ := json.Marshal(pb.Payload{
|
||||
Encrypted: false,
|
||||
Content: bytes,
|
||||
})
|
||||
return &pb.IBTP{
|
||||
From: from,
|
||||
To: to,
|
||||
Payload: ibtppd,
|
||||
Index: nonce,
|
||||
Type: pb.IBTP_INTERCHAIN,
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
}
|
||||
}
|
||||
|
||||
type mockPeerMgr struct {
|
||||
mock.Mock
|
||||
EventChan chan *pb.Message
|
||||
}
|
||||
|
||||
func newMockPeerMgr() *mockPeerMgr {
|
||||
return &mockPeerMgr{}
|
||||
}
|
||||
|
||||
func (mpm *mockPeerMgr) Broadcast(msg *pb.Message) error {
|
||||
mpm.EventChan <- msg
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mpm *mockPeerMgr) AsyncSend(id uint64, msg *pb.Message) error {
|
||||
mpm.EventChan <- msg
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mpm *mockPeerMgr) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mpm *mockPeerMgr) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mpm *mockPeerMgr) SendWithStream(network.Stream, *pb.Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mpm *mockPeerMgr) Send(uint64, *pb.Message) (*pb.Message, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (mpm *mockPeerMgr) Peers() map[uint64]*peer.AddrInfo {
|
||||
peers := make(map[uint64]*peer.AddrInfo, 3)
|
||||
var id1 peer.ID
|
||||
id1 = "peer1"
|
||||
peers[0] = &peer.AddrInfo{ID: id1}
|
||||
id1 = "peer2"
|
||||
peers[1] = &peer.AddrInfo{ID: id1}
|
||||
id1 = "peer3"
|
||||
peers[2] = &peer.AddrInfo{ID: id1}
|
||||
return peers
|
||||
}
|
||||
|
||||
func (mpm *mockPeerMgr) OtherPeers() map[uint64]*peer.AddrInfo {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mpm *mockPeerMgr) SubscribeOrderMessage(ch chan<- events.OrderMessageEvent) event.Subscription {
|
||||
return nil
|
||||
}
|
|
@ -7,7 +7,7 @@ import (
|
|||
"github.com/meshplus/bitxhub-model/pb"
|
||||
)
|
||||
|
||||
// batchStore persists batch into DB, which
|
||||
// batchStore persists batch into DB, only be called by leader.
|
||||
func (mpi *mempoolImpl) batchStore(txList []*pb.Transaction) {
|
||||
batch := mpi.storage.NewBatch()
|
||||
for _, tx := range txList {
|
||||
|
@ -18,7 +18,7 @@ func (mpi *mempoolImpl) batchStore(txList []*pb.Transaction) {
|
|||
batch.Commit()
|
||||
}
|
||||
|
||||
// batchDelete batch delete txs
|
||||
// batchDelete delete txs from DB by given hash list.
|
||||
func (mpi *mempoolImpl) batchDelete(hashes []types.Hash) {
|
||||
batch := mpi.storage.NewBatch()
|
||||
for _, hash := range hashes {
|
||||
|
@ -28,12 +28,6 @@ func (mpi *mempoolImpl) batchDelete(hashes []types.Hash) {
|
|||
batch.Commit()
|
||||
}
|
||||
|
||||
func (mpi *mempoolImpl) store(tx *pb.Transaction) {
|
||||
txKey := compositeKey(tx.TransactionHash.Bytes())
|
||||
txData, _ := tx.Marshal()
|
||||
mpi.storage.Put(txKey, txData)
|
||||
}
|
||||
|
||||
func (mpi *mempoolImpl) load(hash types.Hash) (*pb.Transaction, bool) {
|
||||
txKey := compositeKey(hash.Bytes())
|
||||
txData := mpi.storage.Get(txKey)
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package mempool
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/meshplus/bitxhub-kit/types"
|
||||
"github.com/meshplus/bitxhub-model/pb"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStorage(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
mempool, _ := mockMempoolImpl()
|
||||
defer cleanTestData()
|
||||
|
||||
txList := make([]*pb.Transaction, 0)
|
||||
txHashList := make([]types.Hash, 0)
|
||||
txHash1, _ := hex2Hash("txHash1")
|
||||
tx1 := &pb.Transaction{Nonce: uint64(1), TransactionHash: txHash1}
|
||||
txHash2, _ := hex2Hash("txHash2")
|
||||
tx2 := &pb.Transaction{Nonce: uint64(1), TransactionHash: txHash2}
|
||||
txList = append(txList, tx1, tx2)
|
||||
txHashList = append(txHashList, txHash1, txHash2)
|
||||
mempool.batchStore(txList)
|
||||
tx, ok := mempool.load(txHash1)
|
||||
ast.Equal(true, ok)
|
||||
ast.Equal(uint64(1), tx.Nonce)
|
||||
|
||||
mempool.batchDelete(txHashList)
|
||||
tx, ok = mempool.load(txHash1)
|
||||
ast.Equal(false, ok)
|
||||
ast.Nil(tx)
|
||||
}
|
|
@ -17,9 +17,10 @@ type TxCache struct {
|
|||
stopTimerC chan bool
|
||||
close chan bool
|
||||
txSetTick time.Duration
|
||||
txSetSize uint64
|
||||
}
|
||||
|
||||
func newTxCache(txSliceTimeout time.Duration, logger logrus.FieldLogger) *TxCache {
|
||||
func newTxCache(txSliceTimeout time.Duration, txSetSize uint64, logger logrus.FieldLogger) *TxCache {
|
||||
txCache := &TxCache{}
|
||||
txCache.recvTxC = make(chan *pb.Transaction, DefaultTxCacheSize)
|
||||
txCache.close = make(chan bool)
|
||||
|
@ -33,6 +34,11 @@ func newTxCache(txSliceTimeout time.Duration, logger logrus.FieldLogger) *TxCach
|
|||
} else {
|
||||
txCache.txSetTick = txSliceTimeout
|
||||
}
|
||||
if txSetSize == 0 {
|
||||
txCache.txSetSize = DefaultTxSetSize
|
||||
} else {
|
||||
txCache.txSetSize = txSetSize
|
||||
}
|
||||
return txCache
|
||||
}
|
||||
|
||||
|
@ -62,7 +68,7 @@ func (tc *TxCache) appendTx(tx *pb.Transaction) {
|
|||
tc.startTxSetTimer()
|
||||
}
|
||||
tc.txSet = append(tc.txSet, tx)
|
||||
if len(tc.txSet) >= DefaultTxSetSize {
|
||||
if uint64(len(tc.txSet)) >= tc.txSetSize {
|
||||
tc.stopTxSetTimer()
|
||||
tc.postTxSet()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package mempool
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/meshplus/bitxhub-kit/log"
|
||||
"github.com/meshplus/bitxhub-model/pb"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAppendTx(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
logger := log.NewWithModule("consensus")
|
||||
sliceTimeout := 1 * time.Millisecond
|
||||
txCache := newTxCache(sliceTimeout, 2, logger)
|
||||
go txCache.listenEvent()
|
||||
|
||||
tx := &pb.Transaction{}
|
||||
txCache.appendTx(nil)
|
||||
ast.Equal(0, len(txCache.txSet), "nil transaction")
|
||||
|
||||
tx = &pb.Transaction{Nonce: 1}
|
||||
txCache.appendTx(tx)
|
||||
select {
|
||||
case txSet := <-txCache.txSetC:
|
||||
ast.Equal(1, len(txSet.TxList), "post tx set by timeout")
|
||||
ast.Equal(0, len(txCache.txSet))
|
||||
}
|
||||
txCache.stopTxSetTimer()
|
||||
|
||||
txCache.txSetTick = 1 * time.Second
|
||||
tx1 := &pb.Transaction{Nonce: 2}
|
||||
tx2 := &pb.Transaction{Nonce: 3}
|
||||
go txCache.appendTx(tx1)
|
||||
go txCache.appendTx(tx2)
|
||||
select {
|
||||
case txSet := <-txCache.txSetC:
|
||||
ast.Equal(2, len(txSet.TxList), "post tx set by size")
|
||||
ast.Equal(0, len(txCache.txSet))
|
||||
}
|
||||
// test exit txCache
|
||||
close(txCache.close)
|
||||
}
|
|
@ -72,7 +72,7 @@ func (m *txSortedMap) filterReady(demandNonce uint64) ([]*pb.Transaction, []*pb.
|
|||
if m.index.data.Len() == 0 {
|
||||
return nil, nil, demandNonce
|
||||
}
|
||||
demandKey := makeSortedNonceKeyKey(demandNonce)
|
||||
demandKey := makeSortedNonceKey(demandNonce)
|
||||
m.index.data.AscendGreaterOrEqual(demandKey, func(i btree.Item) bool {
|
||||
nonce := i.(*sortedNonceKey).nonce
|
||||
if nonce == demandNonce {
|
||||
|
@ -91,7 +91,7 @@ func (m *txSortedMap) filterReady(demandNonce uint64) ([]*pb.Transaction, []*pb.
|
|||
// provided commitNonce.
|
||||
func (m *txSortedMap) forward(commitNonce uint64) map[string][]*pb.Transaction {
|
||||
removedTxs := make(map[string][]*pb.Transaction)
|
||||
commitNonceKey := makeSortedNonceKeyKey(commitNonce)
|
||||
commitNonceKey := makeSortedNonceKey(commitNonce)
|
||||
m.index.data.AscendLessThan(commitNonceKey, func(i btree.Item) bool {
|
||||
// delete tx from map.
|
||||
nonce := i.(*sortedNonceKey).nonce
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package mempool
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/meshplus/bitxhub-model/pb"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestForward(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
mpi, _ := mockMempoolImpl()
|
||||
defer cleanTestData()
|
||||
|
||||
txList := make([]*pb.Transaction, 0)
|
||||
privKey1 := genPrivKey()
|
||||
account1, _ := privKey1.PublicKey().Address()
|
||||
tx1 := constructTx(uint64(1), &privKey1)
|
||||
tx2 := constructTx(uint64(2), &privKey1)
|
||||
tx3 := constructTx(uint64(3), &privKey1)
|
||||
tx4 := constructTx(uint64(4), &privKey1)
|
||||
tx5 := constructTx(uint64(6), &privKey1)
|
||||
txList = append(txList, tx1, tx2, tx3, tx4, tx5)
|
||||
err := mpi.processTransactions(txList)
|
||||
ast.Nil(err)
|
||||
list := mpi.txStore.allTxs[account1.Hex()]
|
||||
ast.Equal(5, list.index.size())
|
||||
ast.Equal(4, mpi.txStore.priorityIndex.size())
|
||||
ast.Equal(1, mpi.txStore.parkingLotIndex.size())
|
||||
|
||||
removeList := list.forward(uint64(3))
|
||||
ast.Equal(1, len(removeList))
|
||||
ast.Equal(2, len(removeList[account1.Hex()]))
|
||||
ast.Equal(uint64(1), removeList[account1.Hex()][0].Nonce)
|
||||
ast.Equal(uint64(2), removeList[account1.Hex()][1].Nonce)
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package mempool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/meshplus/bitxhub-model/pb"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetAccount(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
privKey := genPrivKey()
|
||||
address, _ := privKey.PublicKey().Address()
|
||||
tx := constructIBTPTx(uint64(1), &privKey)
|
||||
addr, err := getAccount(tx)
|
||||
ast.Nil(err)
|
||||
expectedAddr := fmt.Sprintf("%s-%s-%d", address, address, pb.IBTP_INTERCHAIN)
|
||||
ast.Equal(expectedAddr, addr)
|
||||
|
||||
data := &pb.TransactionData{
|
||||
Payload: []byte("test"),
|
||||
}
|
||||
tx = &pb.Transaction{
|
||||
To: InterchainContractAddr,
|
||||
Data: data,
|
||||
}
|
||||
_, err = getAccount(tx)
|
||||
ast.NotNil(err.Error(), "unmarshal invoke payload faile")
|
||||
}
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/meshplus/bitxhub-model/pb"
|
||||
"github.com/meshplus/bitxhub/internal/constant"
|
||||
raftproto "github.com/meshplus/bitxhub/pkg/order/etcdraft/proto"
|
||||
|
||||
cmap "github.com/orcaman/concurrent-map"
|
||||
)
|
||||
|
||||
|
@ -23,14 +24,6 @@ func (mpi *mempoolImpl) increaseBatchSeqNo() {
|
|||
atomic.AddUint64(&mpi.batchSeqNo, 1)
|
||||
}
|
||||
|
||||
// getTxByTxPointer returns the tx stored in allTxs by given TxPointer.
|
||||
func (mpi *mempoolImpl) getTxByTxPointer(txPointer orderedIndexKey) *pb.Transaction {
|
||||
if txnMap, ok := mpi.txStore.allTxs[txPointer.account]; ok {
|
||||
return txnMap.items[txPointer.nonce].tx
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mpi *mempoolImpl) msgToConsensusPbMsg(data []byte, tyr raftproto.RaftMessage_Type) *pb.Message {
|
||||
rm := &raftproto.RaftMessage{
|
||||
Type: tyr,
|
||||
|
@ -69,6 +62,7 @@ func newNonceCache() *nonceCache {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO (YH): refactor the tx struct
|
||||
func hex2Hash(hash string) (types.Hash, error) {
|
||||
var (
|
||||
hubHash types.Hash
|
||||
|
@ -137,7 +131,7 @@ func getAccount(tx *pb.Transaction) (string, error) {
|
|||
}
|
||||
payload := &pb.InvokePayload{}
|
||||
if err := payload.Unmarshal(tx.Data.Payload); err != nil {
|
||||
return "", fmt.Errorf("unmarshal invoke payload: %s", err.Error())
|
||||
return "", fmt.Errorf("unmarshal invoke payload failed: %s", err.Error())
|
||||
}
|
||||
if payload.Method == IBTPMethod1 || payload.Method == IBTPMethod2 {
|
||||
ibtp := &pb.IBTP{}
|
||||
|
|
Loading…
Reference in New Issue