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