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 }