github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/test/wallet_test_util.go (about)

     1  package test
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path"
     8  	"reflect"
     9  
    10  	"github.com/bytom/bytom/account"
    11  	"github.com/bytom/bytom/asset"
    12  	"github.com/bytom/bytom/blockchain/pseudohsm"
    13  	"github.com/bytom/bytom/blockchain/signers"
    14  	"github.com/bytom/bytom/contract"
    15  	"github.com/bytom/bytom/crypto/ed25519/chainkd"
    16  	dbm "github.com/bytom/bytom/database/leveldb"
    17  	"github.com/bytom/bytom/event"
    18  	"github.com/bytom/bytom/protocol"
    19  	"github.com/bytom/bytom/protocol/bc/types"
    20  	w "github.com/bytom/bytom/wallet"
    21  )
    22  
    23  type walletTestConfig struct {
    24  	Keys       []*keyInfo     `json:"keys"`
    25  	Accounts   []*accountInfo `json:"accounts"`
    26  	Blocks     []*wtBlock     `json:"blocks"`
    27  	RollbackTo uint64         `json:"rollback_to"`
    28  }
    29  
    30  type keyInfo struct {
    31  	Name     string `json:"name"`
    32  	Password string `json:"password"`
    33  }
    34  
    35  type accountInfo struct {
    36  	Name   string   `json:"name"`
    37  	Keys   []string `json:"keys"`
    38  	Quorum int      `json:"quorum"`
    39  }
    40  
    41  type wtBlock struct {
    42  	CoinbaseAccount string            `json:"coinbase_account"`
    43  	Transactions    []*wtTransaction  `json:"transactions"`
    44  	PostStates      []*accountBalance `json:"post_states"`
    45  	Append          uint64            `json:"append"`
    46  }
    47  
    48  func (b *wtBlock) create(ctx *walletTestContext) (*types.Block, error) {
    49  	transactions := make([]*types.Tx, 0, len(b.Transactions))
    50  	for _, t := range b.Transactions {
    51  		tx, err := t.create(ctx)
    52  		if err != nil {
    53  			continue
    54  		}
    55  		transactions = append(transactions, tx)
    56  	}
    57  	return ctx.newBlock(transactions, b.CoinbaseAccount)
    58  }
    59  
    60  func (b *wtBlock) verifyPostStates(ctx *walletTestContext) error {
    61  	for _, state := range b.PostStates {
    62  		balance, err := ctx.getBalance(state.AccountAlias, state.AssetAlias)
    63  		if err != nil {
    64  			return err
    65  		}
    66  
    67  		if balance != state.Amount {
    68  			return fmt.Errorf("AccountAlias: %s, AssetAlias: %s, expected: %d, have: %d", state.AccountAlias, state.AssetAlias, state.Amount, balance)
    69  		}
    70  	}
    71  	return nil
    72  }
    73  
    74  type wtTransaction struct {
    75  	Passwords []string  `json:"passwords"`
    76  	Inputs    []*action `json:"inputs"`
    77  	Outputs   []*action `json:"outputs"`
    78  }
    79  
    80  // create signed transaction
    81  func (t *wtTransaction) create(ctx *walletTestContext) (*types.Tx, error) {
    82  	generator := NewTxGenerator(ctx.Wallet.AccountMgr, ctx.Wallet.AssetReg, ctx.Wallet.Hsm)
    83  	for _, input := range t.Inputs {
    84  		switch input.Type {
    85  		case "spend_account":
    86  			if err := generator.AddSpendInput(input.AccountAlias, input.AssetAlias, input.Amount); err != nil {
    87  				return nil, err
    88  			}
    89  		case "issue":
    90  			_, err := ctx.createAsset(input.AccountAlias, input.AssetAlias)
    91  			if err != nil {
    92  				return nil, err
    93  			}
    94  			if err := generator.AddIssuanceInput(input.AssetAlias, input.Amount); err != nil {
    95  				return nil, err
    96  			}
    97  		}
    98  	}
    99  
   100  	for _, output := range t.Outputs {
   101  		switch output.Type {
   102  		case "output":
   103  			if err := generator.AddTxOutput(output.AccountAlias, output.AssetAlias, output.Amount); err != nil {
   104  				return nil, err
   105  			}
   106  		case "retire":
   107  			if err := generator.AddRetirement(output.AssetAlias, output.Amount); err != nil {
   108  				return nil, err
   109  			}
   110  		}
   111  	}
   112  	return generator.Sign(t.Passwords)
   113  }
   114  
   115  type action struct {
   116  	Type         string `json:"type"`
   117  	AccountAlias string `json:"name"`
   118  	AssetAlias   string `json:"asset"`
   119  	Amount       uint64 `json:"amount"`
   120  }
   121  
   122  type accountBalance struct {
   123  	AssetAlias   string `json:"asset"`
   124  	AccountAlias string `json:"name"`
   125  	Amount       uint64 `json:"amount"`
   126  }
   127  
   128  type walletTestContext struct {
   129  	Wallet *w.Wallet
   130  	Chain  *protocol.Chain
   131  }
   132  
   133  func (ctx *walletTestContext) createControlProgram(accountName string, change bool) (*account.CtrlProgram, error) {
   134  	acc, err := ctx.Wallet.AccountMgr.FindByAlias(accountName)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	return ctx.Wallet.AccountMgr.CreateAddress(acc.ID, change)
   140  }
   141  
   142  func (ctx *walletTestContext) getPubkey(keyAlias string) *chainkd.XPub {
   143  	pubKeys := ctx.Wallet.Hsm.ListKeys()
   144  	for i, key := range pubKeys {
   145  		if key.Alias == keyAlias {
   146  			return &pubKeys[i].XPub
   147  		}
   148  	}
   149  	return nil
   150  }
   151  
   152  func (ctx *walletTestContext) createAsset(accountAlias string, assetAlias string) (*asset.Asset, error) {
   153  	acc, err := ctx.Wallet.AccountMgr.FindByAlias(accountAlias)
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  	return ctx.Wallet.AssetReg.Define(acc.XPubs, len(acc.XPubs), nil, 0, assetAlias, nil)
   158  }
   159  
   160  func (ctx *walletTestContext) newBlock(txs []*types.Tx, coinbaseAccount string) (*types.Block, error) {
   161  	controlProgram, err := ctx.createControlProgram(coinbaseAccount, true)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	return NewBlock(ctx.Chain, txs, controlProgram.ControlProgram)
   166  }
   167  
   168  func (ctx *walletTestContext) createKey(name string, password string) error {
   169  	_, _, err := ctx.Wallet.Hsm.XCreate(name, password, "en")
   170  	return err
   171  }
   172  
   173  func (ctx *walletTestContext) createAccount(name string, keys []string, quorum int) error {
   174  	xpubs := []chainkd.XPub{}
   175  	for _, alias := range keys {
   176  		xpub := ctx.getPubkey(alias)
   177  		if xpub == nil {
   178  			return fmt.Errorf("can't find pubkey for %s", alias)
   179  		}
   180  		xpubs = append(xpubs, *xpub)
   181  	}
   182  	_, err := ctx.Wallet.AccountMgr.Create(xpubs, quorum, name, signers.BIP0044)
   183  	return err
   184  }
   185  
   186  func (ctx *walletTestContext) update(block *types.Block) error {
   187  	if _, err := ctx.Chain.ProcessBlock(block); err != nil {
   188  		return err
   189  	}
   190  	if err := ctx.Wallet.AttachBlock(block); err != nil {
   191  		return err
   192  	}
   193  	return nil
   194  }
   195  
   196  func (ctx *walletTestContext) getBalance(accountAlias string, assetAlias string) (uint64, error) {
   197  	balances, _ := ctx.Wallet.GetAccountBalances("", "")
   198  	for _, balance := range balances {
   199  		if balance.Alias == accountAlias && balance.AssetAlias == assetAlias {
   200  			return balance.Amount, nil
   201  		}
   202  	}
   203  	return 0, nil
   204  }
   205  
   206  func (ctx *walletTestContext) getAccBalances() map[string]map[string]uint64 {
   207  	accBalances := make(map[string]map[string]uint64)
   208  	balances, _ := ctx.Wallet.GetAccountBalances("", "")
   209  	for _, balance := range balances {
   210  		if accBalance, ok := accBalances[balance.Alias]; ok {
   211  			if _, ok := accBalance[balance.AssetAlias]; ok {
   212  				accBalance[balance.AssetAlias] += balance.Amount
   213  				continue
   214  			}
   215  			accBalance[balance.AssetAlias] = balance.Amount
   216  			continue
   217  		}
   218  		accBalances[balance.Alias] = map[string]uint64{balance.AssetAlias: balance.Amount}
   219  	}
   220  	return accBalances
   221  }
   222  
   223  func (ctx *walletTestContext) getDetachedBlocks(height uint64) ([]*types.Block, error) {
   224  	currentHeight := ctx.Chain.BestBlockHeight()
   225  	detachedBlocks := make([]*types.Block, 0, currentHeight-height)
   226  	for i := currentHeight; i > height; i-- {
   227  		block, err := ctx.Chain.GetBlockByHeight(i)
   228  		if err != nil {
   229  			return detachedBlocks, err
   230  		}
   231  		detachedBlocks = append(detachedBlocks, block)
   232  	}
   233  	return detachedBlocks, nil
   234  }
   235  
   236  func (ctx *walletTestContext) validateRollback(oldAccBalances map[string]map[string]uint64) error {
   237  	accBalances := ctx.getAccBalances()
   238  	if reflect.DeepEqual(oldAccBalances, accBalances) {
   239  		return nil
   240  	}
   241  	return fmt.Errorf("different account balances after rollback")
   242  }
   243  
   244  func (cfg *walletTestConfig) Run() error {
   245  	dirPath, err := ioutil.TempDir(".", "pseudo_hsm")
   246  	if err != nil {
   247  		return err
   248  	}
   249  	defer os.RemoveAll(dirPath)
   250  	hsm, err := pseudohsm.New(dirPath)
   251  	if err != nil {
   252  		return err
   253  	}
   254  
   255  	db := dbm.NewDB("wallet_test_db", "leveldb", path.Join(dirPath, "wallet_test_db"))
   256  	chain, _, _, err := MockChain(db)
   257  	if err != nil {
   258  		return err
   259  	}
   260  	walletDB := dbm.NewDB("wallet", "leveldb", path.Join(dirPath, "wallet_db"))
   261  	accountManager := account.NewManager(walletDB, chain)
   262  	assets := asset.NewRegistry(walletDB, chain)
   263  	contracts := contract.NewRegistry(walletDB)
   264  	dispatcher := event.NewDispatcher()
   265  	wallet, err := w.NewWallet(walletDB, accountManager, assets, contracts, hsm, chain, dispatcher, false)
   266  	if err != nil {
   267  		return err
   268  	}
   269  	ctx := &walletTestContext{
   270  		Wallet: wallet,
   271  		Chain:  chain,
   272  	}
   273  
   274  	for _, key := range cfg.Keys {
   275  		if err := ctx.createKey(key.Name, key.Password); err != nil {
   276  			return err
   277  		}
   278  	}
   279  
   280  	for _, acc := range cfg.Accounts {
   281  		if err := ctx.createAccount(acc.Name, acc.Keys, acc.Quorum); err != nil {
   282  			return err
   283  		}
   284  	}
   285  
   286  	var accBalances map[string]map[string]uint64
   287  	var rollbackBlock *types.Block
   288  	for _, blk := range cfg.Blocks {
   289  		block, err := blk.create(ctx)
   290  		if err != nil {
   291  			return err
   292  		}
   293  		if err := ctx.update(block); err != nil {
   294  			return err
   295  		}
   296  		if err := blk.verifyPostStates(ctx); err != nil {
   297  			return err
   298  		}
   299  		if block.Height <= cfg.RollbackTo && cfg.RollbackTo <= block.Height+blk.Append {
   300  			accBalances = ctx.getAccBalances()
   301  			rollbackBlock = block
   302  		}
   303  		if err := AppendBlocks(ctx.Chain, blk.Append); err != nil {
   304  			return err
   305  		}
   306  	}
   307  
   308  	if rollbackBlock == nil {
   309  		return nil
   310  	}
   311  
   312  	// rollback and validate
   313  	detachedBlocks, err := ctx.getDetachedBlocks(rollbackBlock.Height)
   314  	if err != nil {
   315  		return err
   316  	}
   317  
   318  	forkPath, err := ioutil.TempDir(".", "forked_chain")
   319  	if err != nil {
   320  		return err
   321  	}
   322  
   323  	forkedChain, err := declChain(forkPath, ctx.Chain, rollbackBlock.Height, ctx.Chain.BestBlockHeight()+1)
   324  	defer os.RemoveAll(forkPath)
   325  	if err != nil {
   326  		return err
   327  	}
   328  
   329  	if err := merge(forkedChain, ctx.Chain); err != nil {
   330  		return err
   331  	}
   332  
   333  	for _, block := range detachedBlocks {
   334  		if err := ctx.Wallet.DetachBlock(block); err != nil {
   335  			return err
   336  		}
   337  	}
   338  	return ctx.validateRollback(accBalances)
   339  }