494 lines
14 KiB
Go
494 lines
14 KiB
Go
package executor
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math/rand"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gogo/protobuf/proto"
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/meshplus/bitxhub-kit/crypto"
|
|
"github.com/meshplus/bitxhub-kit/crypto/asym"
|
|
"github.com/meshplus/bitxhub-kit/log"
|
|
"github.com/meshplus/bitxhub-kit/storage/blockfile"
|
|
"github.com/meshplus/bitxhub-kit/storage/leveldb"
|
|
"github.com/meshplus/bitxhub-kit/types"
|
|
"github.com/meshplus/bitxhub-model/constant"
|
|
"github.com/meshplus/bitxhub-model/pb"
|
|
"github.com/meshplus/bitxhub/internal/ledger"
|
|
"github.com/meshplus/bitxhub/internal/ledger/mock_ledger"
|
|
"github.com/meshplus/bitxhub/internal/model/events"
|
|
"github.com/meshplus/bitxhub/internal/repo"
|
|
libp2pcert "github.com/meshplus/go-libp2p-cert"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const (
|
|
keyPassword = "bitxhub"
|
|
srcMethod = "did:bitxhub:appchain1:."
|
|
dstMethod = "did:bitxhub:appchain2:."
|
|
from = "0x3f9d18f7c3a6e5e4c0b877fe3e688ab08840b997"
|
|
executorType = "serial"
|
|
)
|
|
|
|
func TestNew(t *testing.T) {
|
|
mockCtl := gomock.NewController(t)
|
|
mockLedger := mock_ledger.NewMockLedger(mockCtl)
|
|
|
|
// mock data for ledger
|
|
chainMeta := &pb.ChainMeta{
|
|
Height: 1,
|
|
BlockHash: types.NewHashByStr(from),
|
|
}
|
|
mockLedger.EXPECT().GetChainMeta().Return(chainMeta).AnyTimes()
|
|
|
|
logger := log.NewWithModule("executor")
|
|
executor, err := New(mockLedger, logger, executorType)
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, executor)
|
|
|
|
assert.Equal(t, mockLedger, executor.ledger)
|
|
assert.Equal(t, logger, executor.logger)
|
|
assert.NotNil(t, executor.preBlockC)
|
|
assert.NotNil(t, executor.blockC)
|
|
assert.NotNil(t, executor.persistC)
|
|
assert.NotNil(t, executor.ibtpVerify)
|
|
assert.NotNil(t, executor.validationEngine)
|
|
assert.Equal(t, chainMeta.BlockHash, executor.currentBlockHash)
|
|
assert.Equal(t, chainMeta.Height, executor.currentHeight)
|
|
assert.NotNil(t, executor.wasmInstances)
|
|
assert.Equal(t, 0, len(executor.wasmInstances))
|
|
}
|
|
|
|
func TestBlockExecutor_ExecuteBlock(t *testing.T) {
|
|
mockCtl := gomock.NewController(t)
|
|
mockLedger := mock_ledger.NewMockLedger(mockCtl)
|
|
|
|
// mock data for ledger
|
|
chainMeta := &pb.ChainMeta{
|
|
Height: 1,
|
|
BlockHash: types.NewHash([]byte(from)),
|
|
}
|
|
|
|
evs := make([]*pb.Event, 0)
|
|
m := make(map[string]uint64)
|
|
m[from] = 3
|
|
data, err := json.Marshal(m)
|
|
assert.Nil(t, err)
|
|
ev := &pb.Event{
|
|
TxHash: types.NewHash([]byte(from)),
|
|
Data: data,
|
|
Interchain: true,
|
|
}
|
|
evs = append(evs, ev)
|
|
mockLedger.EXPECT().GetChainMeta().Return(chainMeta).AnyTimes()
|
|
mockLedger.EXPECT().Events(gomock.Any()).Return(evs).AnyTimes()
|
|
mockLedger.EXPECT().Commit(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
|
mockLedger.EXPECT().Clear().AnyTimes()
|
|
mockLedger.EXPECT().GetState(gomock.Any(), gomock.Any()).Return(true, []byte("10")).AnyTimes()
|
|
mockLedger.EXPECT().SetState(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
|
|
mockLedger.EXPECT().GetBalance(gomock.Any()).Return(uint64(10)).AnyTimes()
|
|
mockLedger.EXPECT().SetBalance(gomock.Any(), gomock.Any()).AnyTimes()
|
|
mockLedger.EXPECT().SetNonce(gomock.Any(), gomock.Any()).AnyTimes()
|
|
mockLedger.EXPECT().GetNonce(gomock.Any()).Return(uint64(0)).AnyTimes()
|
|
mockLedger.EXPECT().SetCode(gomock.Any(), gomock.Any()).AnyTimes()
|
|
mockLedger.EXPECT().GetCode(gomock.Any()).Return([]byte("10")).AnyTimes()
|
|
mockLedger.EXPECT().PersistExecutionResult(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
|
mockLedger.EXPECT().FlushDirtyDataAndComputeJournal().Return(make(map[string]*ledger.Account), &ledger.BlockJournal{ChangedHash: &types.Hash{}}).AnyTimes()
|
|
mockLedger.EXPECT().PersistBlockData(gomock.Any()).AnyTimes()
|
|
logger := log.NewWithModule("executor")
|
|
|
|
exec, err := New(mockLedger, logger, executorType)
|
|
assert.Nil(t, err)
|
|
|
|
// mock data for block
|
|
var txs []*pb.Transaction
|
|
privKey, err := asym.GenerateKeyPair(crypto.Secp256k1)
|
|
assert.Nil(t, err)
|
|
pubKey := privKey.PublicKey()
|
|
|
|
// set tx of TransactionData_BVM type
|
|
ibtp1 := mockIBTP(t, 1, pb.IBTP_INTERCHAIN)
|
|
BVMData := mockTxData(t, pb.TransactionData_INVOKE, pb.TransactionData_BVM, ibtp1)
|
|
BVMTx := mockTx(t, BVMData)
|
|
txs = append(txs, BVMTx)
|
|
// set tx of TransactionData_XVM type
|
|
ibtp2 := mockIBTP(t, 2, pb.IBTP_INTERCHAIN)
|
|
XVMData := mockTxData(t, pb.TransactionData_INVOKE, pb.TransactionData_XVM, ibtp2)
|
|
XVMTx := mockTx(t, XVMData)
|
|
txs = append(txs, XVMTx)
|
|
// set tx of TransactionData_NORMAL type
|
|
ibtp3 := mockIBTP(t, 3, pb.IBTP_INTERCHAIN)
|
|
NormalData := mockTxData(t, pb.TransactionData_NORMAL, pb.TransactionData_XVM, ibtp3)
|
|
NormalTx := mockTx(t, NormalData)
|
|
txs = append(txs, NormalTx)
|
|
// set tx with empty transaction data
|
|
emptyDataTx := mockTx(t, nil)
|
|
txs = append(txs, emptyDataTx)
|
|
|
|
// set signature for txs
|
|
for _, tx := range txs {
|
|
tx.From, err = pubKey.Address()
|
|
assert.Nil(t, err)
|
|
sig, err := privKey.Sign(tx.SignHash().Bytes())
|
|
assert.Nil(t, err)
|
|
tx.Signature = sig
|
|
tx.TransactionHash = tx.Hash()
|
|
}
|
|
// set invalid signature tx
|
|
invalidTx := mockTx(t, nil)
|
|
invalidTx.From = types.NewAddressByStr(from)
|
|
invalidTx.Signature = []byte("invalid")
|
|
txs = append(txs, invalidTx)
|
|
|
|
assert.Nil(t, exec.Start())
|
|
|
|
done := make(chan bool)
|
|
ch := make(chan events.ExecutedEvent)
|
|
blockSub := exec.SubscribeBlockEvent(ch)
|
|
defer blockSub.Unsubscribe()
|
|
|
|
// count received block to end test
|
|
var wg sync.WaitGroup
|
|
wg.Add(2)
|
|
go listenBlock(&wg, done, ch)
|
|
|
|
// send blocks to executor
|
|
commitEvent1 := mockCommitEvent(uint64(1), nil)
|
|
commitEvent2 := mockCommitEvent(uint64(2), txs)
|
|
exec.ExecuteBlock(commitEvent1)
|
|
exec.ExecuteBlock(commitEvent2)
|
|
|
|
wg.Wait()
|
|
done <- true
|
|
assert.Nil(t, exec.Stop())
|
|
}
|
|
|
|
func TestBlockExecutor_ApplyReadonlyTransactions(t *testing.T) {
|
|
mockCtl := gomock.NewController(t)
|
|
mockLedger := mock_ledger.NewMockLedger(mockCtl)
|
|
|
|
// mock data for ledger
|
|
chainMeta := &pb.ChainMeta{
|
|
Height: 1,
|
|
BlockHash: types.NewHashByStr(from),
|
|
}
|
|
|
|
privKey, err := asym.GenerateKeyPair(crypto.Secp256k1)
|
|
assert.Nil(t, err)
|
|
|
|
id := fmt.Sprintf("%s-%s-%d", srcMethod, dstMethod, 1)
|
|
|
|
hash := types.NewHash([]byte{1})
|
|
val, err := json.Marshal(hash)
|
|
assert.Nil(t, err)
|
|
|
|
contractAddr := constant.InterchainContractAddr.Address()
|
|
|
|
mockLedger.EXPECT().GetChainMeta().Return(chainMeta).AnyTimes()
|
|
mockLedger.EXPECT().Events(gomock.Any()).Return(nil).AnyTimes()
|
|
mockLedger.EXPECT().Commit(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
|
mockLedger.EXPECT().Clear().AnyTimes()
|
|
mockLedger.EXPECT().GetState(contractAddr, []byte(fmt.Sprintf("index-tx-%s", id))).Return(true, val).AnyTimes()
|
|
mockLedger.EXPECT().PersistExecutionResult(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
|
mockLedger.EXPECT().FlushDirtyDataAndComputeJournal().Return(make(map[string]*ledger.Account), &ledger.BlockJournal{}).AnyTimes()
|
|
mockLedger.EXPECT().PersistBlockData(gomock.Any()).AnyTimes()
|
|
mockLedger.EXPECT().GetNonce(gomock.Any()).Return(uint64(0)).AnyTimes()
|
|
mockLedger.EXPECT().SetNonce(gomock.Any(), gomock.Any()).AnyTimes()
|
|
logger := log.NewWithModule("executor")
|
|
|
|
exec, err := New(mockLedger, logger, executorType)
|
|
assert.Nil(t, err)
|
|
|
|
// mock data for block
|
|
var txs []*pb.Transaction
|
|
tx, err := genBVMContractTransaction(privKey, 1, contractAddr, "GetIBTPByID", pb.String(id))
|
|
assert.Nil(t, err)
|
|
|
|
txs = append(txs, tx)
|
|
receipts := exec.ApplyReadonlyTransactions(txs)
|
|
|
|
assert.Equal(t, 1, len(receipts))
|
|
assert.Equal(t, hash.Bytes(), receipts[0].Ret)
|
|
assert.Equal(t, pb.Receipt_SUCCESS, receipts[0].Status)
|
|
}
|
|
|
|
func listenBlock(wg *sync.WaitGroup, done chan bool, blockCh chan events.ExecutedEvent) {
|
|
for {
|
|
select {
|
|
case <-blockCh:
|
|
wg.Done()
|
|
case <-done:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func mockCommitEvent(blockNumber uint64, txs []*pb.Transaction) *pb.CommitEvent {
|
|
block := mockBlock(blockNumber, txs)
|
|
localList := make([]bool, len(block.Transactions))
|
|
for i := 0; i < len(block.Transactions); i++ {
|
|
localList[i] = false
|
|
}
|
|
return &pb.CommitEvent{
|
|
Block: block,
|
|
LocalList: localList,
|
|
}
|
|
}
|
|
|
|
func mockBlock(blockNumber uint64, txs []*pb.Transaction) *pb.Block {
|
|
header := &pb.BlockHeader{
|
|
Number: blockNumber,
|
|
Timestamp: time.Now().UnixNano(),
|
|
}
|
|
block := &pb.Block{
|
|
BlockHeader: header,
|
|
Transactions: txs,
|
|
}
|
|
block.BlockHash = block.Hash()
|
|
|
|
return block
|
|
}
|
|
|
|
func mockTx(t *testing.T, data *pb.TransactionData) *pb.Transaction {
|
|
var content []byte
|
|
if data != nil {
|
|
content, _ = data.Marshal()
|
|
}
|
|
return &pb.Transaction{
|
|
To: randAddress(t),
|
|
Payload: content,
|
|
Nonce: uint64(rand.Int63()),
|
|
}
|
|
}
|
|
|
|
func TestBlockExecutor_ExecuteBlock_Transfer(t *testing.T) {
|
|
repoRoot, err := ioutil.TempDir("", "executor")
|
|
require.Nil(t, err)
|
|
|
|
blockchainStorage, err := leveldb.New(filepath.Join(repoRoot, "storage"))
|
|
require.Nil(t, err)
|
|
ldb, err := leveldb.New(filepath.Join(repoRoot, "ledger"))
|
|
require.Nil(t, err)
|
|
|
|
repo.DefaultConfig()
|
|
accountCache, err := ledger.NewAccountCache()
|
|
assert.Nil(t, err)
|
|
logger := log.NewWithModule("executor_test")
|
|
blockFile, err := blockfile.NewBlockFile(repoRoot, logger)
|
|
assert.Nil(t, err)
|
|
ldg, err := ledger.New(createMockRepo(t), blockchainStorage, ldb, blockFile, accountCache, log.NewWithModule("ledger"))
|
|
require.Nil(t, err)
|
|
|
|
_, from := loadAdminKey(t)
|
|
|
|
ldg.SetBalance(from, 100000000)
|
|
account, journal := ldg.FlushDirtyDataAndComputeJournal()
|
|
err = ldg.Commit(1, account, journal)
|
|
require.Nil(t, err)
|
|
err = ldg.PersistExecutionResult(mockBlock(1, nil), nil, &pb.InterchainMeta{})
|
|
require.Nil(t, err)
|
|
|
|
executor, err := New(ldg, log.NewWithModule("executor"), executorType)
|
|
require.Nil(t, err)
|
|
err = executor.Start()
|
|
require.Nil(t, err)
|
|
|
|
ch := make(chan events.ExecutedEvent)
|
|
sub := executor.SubscribeBlockEvent(ch)
|
|
defer sub.Unsubscribe()
|
|
|
|
var txs []*pb.Transaction
|
|
txs = append(txs, mockTransferTx(t))
|
|
txs = append(txs, mockTransferTx(t))
|
|
txs = append(txs, mockTransferTx(t))
|
|
commitEvent := mockCommitEvent(2, txs)
|
|
executor.ExecuteBlock(commitEvent)
|
|
require.Nil(t, err)
|
|
|
|
block := <-ch
|
|
require.EqualValues(t, 2, block.Block.Height())
|
|
require.EqualValues(t, uint64(99999997), ldg.GetBalance(from))
|
|
|
|
// test executor with readonly ledger
|
|
viewLedger, err := ledger.New(createMockRepo(t), blockchainStorage, ldb, blockFile, accountCache, log.NewWithModule("ledger"))
|
|
require.Nil(t, err)
|
|
|
|
exec, err := New(viewLedger, log.NewWithModule("executor"), executorType)
|
|
require.Nil(t, err)
|
|
|
|
tx := mockTransferTx(t)
|
|
receipts := exec.ApplyReadonlyTransactions([]*pb.Transaction{tx})
|
|
require.NotNil(t, receipts)
|
|
require.Equal(t, pb.Receipt_SUCCESS, receipts[0].Status)
|
|
require.Nil(t, receipts[0].Ret)
|
|
}
|
|
|
|
func mockTransferTx(t *testing.T) *pb.Transaction {
|
|
privKey, from := loadAdminKey(t)
|
|
to := randAddress(t)
|
|
|
|
transactionData := &pb.TransactionData{
|
|
Type: pb.TransactionData_NORMAL,
|
|
Amount: 1,
|
|
}
|
|
|
|
data, err := transactionData.Marshal()
|
|
require.Nil(t, err)
|
|
|
|
tx := &pb.Transaction{
|
|
From: from,
|
|
To: to,
|
|
Timestamp: time.Now().UnixNano(),
|
|
Payload: data,
|
|
Amount: 1,
|
|
}
|
|
|
|
err = tx.Sign(privKey)
|
|
require.Nil(t, err)
|
|
tx.TransactionHash = tx.Hash()
|
|
|
|
return tx
|
|
}
|
|
|
|
func loadAdminKey(t *testing.T) (crypto.PrivateKey, *types.Address) {
|
|
privKey, err := asym.RestorePrivateKey(filepath.Join("testdata", "key.json"), keyPassword)
|
|
require.Nil(t, err)
|
|
|
|
from, err := privKey.PublicKey().Address()
|
|
require.Nil(t, err)
|
|
|
|
return privKey, from
|
|
}
|
|
|
|
func randAddress(t *testing.T) *types.Address {
|
|
privKey, err := asym.GenerateKeyPair(crypto.Secp256k1)
|
|
require.Nil(t, err)
|
|
address, err := privKey.PublicKey().Address()
|
|
require.Nil(t, err)
|
|
|
|
return address
|
|
}
|
|
|
|
func genBVMContractTransaction(privateKey crypto.PrivateKey, nonce uint64, address *types.Address, method string, args ...*pb.Arg) (*pb.Transaction, error) {
|
|
return genContractTransaction(pb.TransactionData_BVM, privateKey, nonce, address, method, args...)
|
|
}
|
|
|
|
func genXVMContractTransaction(privateKey crypto.PrivateKey, nonce uint64, address *types.Address, method string, args ...*pb.Arg) (*pb.Transaction, error) {
|
|
return genContractTransaction(pb.TransactionData_XVM, privateKey, nonce, address, method, args...)
|
|
}
|
|
|
|
func genContractTransaction(vmType pb.TransactionData_VMType, privateKey crypto.PrivateKey, nonce uint64, address *types.Address, method string, args ...*pb.Arg) (*pb.Transaction, error) {
|
|
from, err := privateKey.PublicKey().Address()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pl := &pb.InvokePayload{
|
|
Method: method,
|
|
Args: args[:],
|
|
}
|
|
|
|
data, err := pl.Marshal()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
td := &pb.TransactionData{
|
|
Type: pb.TransactionData_INVOKE,
|
|
VmType: vmType,
|
|
Payload: data,
|
|
}
|
|
|
|
pld, err := td.Marshal()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tx := &pb.Transaction{
|
|
From: from,
|
|
To: address,
|
|
Payload: pld,
|
|
Timestamp: time.Now().UnixNano(),
|
|
Nonce: nonce,
|
|
}
|
|
|
|
if err := tx.Sign(privateKey); err != nil {
|
|
return nil, fmt.Errorf("tx sign: %w", err)
|
|
}
|
|
|
|
tx.TransactionHash = tx.Hash()
|
|
|
|
return tx, nil
|
|
}
|
|
|
|
func mockTxData(t *testing.T, dataType pb.TransactionData_Type, vmType pb.TransactionData_VMType, ibtp proto.Marshaler) *pb.TransactionData {
|
|
ib, err := ibtp.Marshal()
|
|
assert.Nil(t, err)
|
|
|
|
tmpIP := &pb.InvokePayload{
|
|
Method: "set",
|
|
Args: []*pb.Arg{{Value: ib}},
|
|
}
|
|
pd, err := tmpIP.Marshal()
|
|
assert.Nil(t, err)
|
|
|
|
return &pb.TransactionData{
|
|
VmType: vmType,
|
|
Type: dataType,
|
|
Amount: 10,
|
|
Payload: pd,
|
|
}
|
|
}
|
|
|
|
func mockIBTP(t *testing.T, index uint64, typ pb.IBTP_Type) *pb.IBTP {
|
|
content := pb.Content{
|
|
SrcContractId: from,
|
|
DstContractId: from,
|
|
Func: "set",
|
|
}
|
|
|
|
bytes, err := content.Marshal()
|
|
assert.Nil(t, err)
|
|
|
|
ibtppd, err := json.Marshal(pb.Payload{
|
|
Encrypted: false,
|
|
Content: bytes,
|
|
})
|
|
assert.Nil(t, err)
|
|
|
|
return &pb.IBTP{
|
|
From: from,
|
|
To: from,
|
|
Payload: ibtppd,
|
|
Index: index,
|
|
Type: typ,
|
|
Timestamp: time.Now().UnixNano(),
|
|
}
|
|
}
|
|
|
|
func createMockRepo(t *testing.T) *repo.Repo {
|
|
key := `-----BEGIN EC PRIVATE KEY-----
|
|
BcNwjTDCxyxLNjFKQfMAc6sY6iJs+Ma59WZyC/4uhjE=
|
|
-----END EC PRIVATE KEY-----`
|
|
|
|
privKey, err := libp2pcert.ParsePrivateKey([]byte(key), crypto.Secp256k1)
|
|
require.Nil(t, err)
|
|
|
|
address, err := privKey.PublicKey().Address()
|
|
require.Nil(t, err)
|
|
|
|
return &repo.Repo{
|
|
Key: &repo.Key{
|
|
PrivKey: privKey,
|
|
Address: address.String(),
|
|
},
|
|
}
|
|
}
|