github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/account/protocol.go (about)

     1  // Copyright (c) 2019 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package account
     7  
     8  import (
     9  	"context"
    10  	"math/big"
    11  
    12  	"github.com/pkg/errors"
    13  	"go.uber.org/zap"
    14  
    15  	"github.com/iotexproject/go-pkgs/hash"
    16  	"github.com/iotexproject/iotex-address/address"
    17  	"github.com/iotexproject/iotex-core/action"
    18  	"github.com/iotexproject/iotex-core/action/protocol"
    19  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    20  	"github.com/iotexproject/iotex-core/pkg/log"
    21  	"github.com/iotexproject/iotex-core/state"
    22  )
    23  
    24  // protocolID is the protocol ID
    25  // TODO: it works only for one instance per protocol definition now
    26  const protocolID = "account"
    27  
    28  // Protocol defines the protocol of handling account
    29  type Protocol struct {
    30  	addr       address.Address
    31  	depositGas DepositGas
    32  }
    33  
    34  // DepositGas deposits gas to some pool
    35  type DepositGas func(ctx context.Context, sm protocol.StateManager, amount *big.Int) (*action.TransactionLog, error)
    36  
    37  // NewProtocol instantiates the protocol of account
    38  func NewProtocol(depositGas DepositGas) *Protocol {
    39  	h := hash.Hash160b([]byte(protocolID))
    40  	addr, err := address.FromBytes(h[:])
    41  	if err != nil {
    42  		log.L().Panic("Error when constructing the address of account protocol", zap.Error(err))
    43  	}
    44  
    45  	return &Protocol{addr: addr, depositGas: depositGas}
    46  }
    47  
    48  // ProtocolAddr returns the address generated from protocol id
    49  func ProtocolAddr() address.Address {
    50  	return protocol.HashStringToAddress(protocolID)
    51  }
    52  
    53  // FindProtocol finds the registered protocol from registry
    54  func FindProtocol(registry *protocol.Registry) *Protocol {
    55  	if registry == nil {
    56  		return nil
    57  	}
    58  	p, ok := registry.Find(protocolID)
    59  	if !ok {
    60  		return nil
    61  	}
    62  	ap, ok := p.(*Protocol)
    63  	if !ok {
    64  		log.S().Panic("fail to cast account protocol")
    65  	}
    66  	return ap
    67  }
    68  
    69  // Handle handles an account
    70  func (p *Protocol) Handle(ctx context.Context, act action.Action, sm protocol.StateManager) (*action.Receipt, error) {
    71  	switch act := act.(type) {
    72  	case *action.Transfer:
    73  		return p.handleTransfer(ctx, act, sm)
    74  	}
    75  	return nil, nil
    76  }
    77  
    78  // Validate validates an account action
    79  func (p *Protocol) Validate(ctx context.Context, act action.Action, sr protocol.StateReader) error {
    80  	switch act := act.(type) {
    81  	case *action.Transfer:
    82  		if err := p.validateTransfer(ctx, act); err != nil {
    83  			return errors.Wrap(err, "error when validating transfer action")
    84  		}
    85  	}
    86  	return nil
    87  }
    88  
    89  // ReadState read the state on blockchain via protocol
    90  func (p *Protocol) ReadState(context.Context, protocol.StateReader, []byte, ...[]byte) ([]byte, uint64, error) {
    91  	return nil, uint64(0), protocol.ErrUnimplemented
    92  }
    93  
    94  // Register registers the protocol with a unique ID
    95  func (p *Protocol) Register(r *protocol.Registry) error {
    96  	return r.Register(protocolID, p)
    97  }
    98  
    99  // ForceRegister registers the protocol with a unique ID and force replacing the previous protocol if it exists
   100  func (p *Protocol) ForceRegister(r *protocol.Registry) error {
   101  	return r.ForceRegister(protocolID, p)
   102  }
   103  
   104  // Name returns the name of protocol
   105  func (p *Protocol) Name() string {
   106  	return protocolID
   107  }
   108  
   109  func createAccount(sm protocol.StateManager, encodedAddr string, init *big.Int, opts ...state.AccountCreationOption) error {
   110  	account := &state.Account{}
   111  	addr, err := address.FromString(encodedAddr)
   112  	if err != nil {
   113  		return errors.Wrap(err, "failed to get address public key hash from encoded address")
   114  	}
   115  	addrHash := hash.BytesToHash160(addr.Bytes())
   116  	_, err = sm.State(account, protocol.LegacyKeyOption(addrHash))
   117  	switch errors.Cause(err) {
   118  	case nil:
   119  		return errors.Errorf("failed to create account %s", encodedAddr)
   120  	case state.ErrStateNotExist:
   121  		account, err := state.NewAccount(opts...)
   122  		if err != nil {
   123  			return err
   124  		}
   125  		if err := account.AddBalance(init); err != nil {
   126  			return errors.Wrapf(err, "failed to add balance %s", init)
   127  		}
   128  		if _, err := sm.PutState(account, protocol.LegacyKeyOption(addrHash)); err != nil {
   129  			return errors.Wrapf(err, "failed to put state for account %x", addrHash)
   130  		}
   131  		return nil
   132  	}
   133  	return err
   134  }
   135  
   136  // CreateGenesisStates initializes the protocol by setting the initial balances to some addresses
   137  func (p *Protocol) CreateGenesisStates(ctx context.Context, sm protocol.StateManager) error {
   138  	blkCtx := protocol.MustGetBlockCtx(ctx)
   139  	g := genesis.MustExtractGenesisContext(ctx)
   140  	if err := p.assertZeroBlockHeight(blkCtx.BlockHeight); err != nil {
   141  		return err
   142  	}
   143  	addrs, amounts := g.InitBalances()
   144  	if err := p.assertEqualLength(addrs, amounts); err != nil {
   145  		return err
   146  	}
   147  	if err := p.assertAmounts(amounts); err != nil {
   148  		return err
   149  	}
   150  	opts := []state.AccountCreationOption{}
   151  	if protocol.MustGetFeatureCtx(ctx).CreateLegacyNonceAccount {
   152  		opts = append(opts, state.LegacyNonceAccountTypeOption())
   153  	}
   154  	for i, addr := range addrs {
   155  		if err := createAccount(sm, addr.String(), amounts[i], opts...); err != nil {
   156  			return err
   157  		}
   158  	}
   159  	return nil
   160  }
   161  
   162  func (p *Protocol) assertZeroBlockHeight(height uint64) error {
   163  	if height != 0 {
   164  		return errors.Errorf("current block height %d is not zero", height)
   165  	}
   166  	return nil
   167  }
   168  
   169  func (p *Protocol) assertEqualLength(addrs []address.Address, amounts []*big.Int) error {
   170  	if len(addrs) != len(amounts) {
   171  		return errors.Errorf(
   172  			"address slice length %d and amounts slice length %d don't match",
   173  			len(addrs),
   174  			len(amounts),
   175  		)
   176  	}
   177  	return nil
   178  }
   179  
   180  func (p *Protocol) assertAmounts(amounts []*big.Int) error {
   181  	for _, amount := range amounts {
   182  		if amount.Cmp(big.NewInt(0)) < 0 {
   183  			return errors.Errorf("account amount %s shouldn't be negative", amount.String())
   184  		}
   185  	}
   186  	return nil
   187  }