github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/poll/consortium.go (about) 1 package poll 2 3 import ( 4 "context" 5 "math/big" 6 "strings" 7 "time" 8 9 "github.com/ethereum/go-ethereum/accounts/abi" 10 "github.com/ethereum/go-ethereum/common" 11 "github.com/ethereum/go-ethereum/common/hexutil" 12 "github.com/ethereum/go-ethereum/crypto" 13 "github.com/iotexproject/go-pkgs/hash" 14 "github.com/iotexproject/iotex-address/address" 15 "github.com/iotexproject/iotex-proto/golang/iotextypes" 16 "github.com/pkg/errors" 17 "go.uber.org/zap" 18 19 "github.com/iotexproject/iotex-core/action" 20 "github.com/iotexproject/iotex-core/action/protocol" 21 "github.com/iotexproject/iotex-core/action/protocol/execution/evm" 22 "github.com/iotexproject/iotex-core/action/protocol/rolldpos" 23 "github.com/iotexproject/iotex-core/blockchain/genesis" 24 "github.com/iotexproject/iotex-core/pkg/log" 25 "github.com/iotexproject/iotex-core/state" 26 ) 27 28 var ( 29 _consortiumCommitteeContractCreator = address.ZeroAddress 30 _consortiumCommitteeContractNonce = uint64(0) 31 // this is a special execution that is not signed, set hash = hex-string of "_consortiumCommitteeContractHash" 32 _consortiumCommitteeContractHash, _ = hash.HexStringToHash256("00636f6e736f727469756d436f6d6d6974746565436f6e747261637448617368") 33 ) 34 35 type contractReader interface { 36 Read(ctx context.Context, contract string, data []byte) ([]byte, error) 37 } 38 39 type contractReaderFunc func(context.Context, string, []byte) ([]byte, error) 40 41 func (f contractReaderFunc) Read(ctx context.Context, contract string, data []byte) ([]byte, error) { 42 return f(ctx, contract, data) 43 } 44 45 type consortiumCommittee struct { 46 contractReader contractReader 47 contract string 48 abi abi.ABI 49 bufferEpochNum uint64 50 bufferResult state.CandidateList 51 indexer *CandidateIndexer 52 addr address.Address 53 getBlockHash evm.GetBlockHash 54 } 55 56 // NewConsortiumCommittee creates a committee for consorium chain 57 func NewConsortiumCommittee(indexer *CandidateIndexer, readContract ReadContract, getBlockHash evm.GetBlockHash) (Protocol, error) { 58 abi, err := abi.JSON(strings.NewReader(ConsortiumManagementABI)) 59 if err != nil { 60 return nil, err 61 } 62 63 if readContract == nil { 64 return nil, errors.New("failed to create consortium committee: empty read contract callback") 65 } 66 h := hash.Hash160b([]byte(_protocolID)) 67 addr, err := address.FromBytes(h[:]) 68 if err != nil { 69 return nil, err 70 } 71 return &consortiumCommittee{ 72 contractReader: genContractReaderFromReadContract(readContract, true), 73 abi: abi, 74 addr: addr, 75 indexer: indexer, 76 getBlockHash: getBlockHash, 77 }, nil 78 } 79 80 func (cc *consortiumCommittee) Start(ctx context.Context, sr protocol.StateReader) (interface{}, error) { 81 g := genesis.MustExtractGenesisContext(ctx) 82 if g.ConsortiumCommitteeContractCode == "" { 83 return nil, errors.New("cannot find consortium committee contract in gensis") 84 } 85 86 caller, _ := address.FromString(_consortiumCommitteeContractCreator) 87 ethAddr := crypto.CreateAddress(common.BytesToAddress(caller.Bytes()), _consortiumCommitteeContractNonce) 88 iotxAddr, _ := address.FromBytes(ethAddr.Bytes()) 89 cc.contract = iotxAddr.String() 90 log.L().Debug("Loaded consortium committee contract", zap.String("address", iotxAddr.String())) 91 92 return nil, nil 93 } 94 95 func (cc *consortiumCommittee) CreateGenesisStates(ctx context.Context, sm protocol.StateManager) error { 96 g := genesis.MustExtractGenesisContext(ctx) 97 blkCtx := protocol.MustGetBlockCtx(ctx) 98 blkCtx.Producer, _ = address.FromString(_consortiumCommitteeContractCreator) 99 blkCtx.GasLimit = g.BlockGasLimitByHeight(0) 100 bytes, err := hexutil.Decode(g.ConsortiumCommitteeContractCode) 101 if err != nil { 102 return err 103 } 104 execution, err := action.NewExecution( 105 "", 106 _consortiumCommitteeContractNonce, 107 big.NewInt(0), 108 g.BlockGasLimitByHeight(0), 109 big.NewInt(0), 110 bytes, 111 ) 112 if err != nil { 113 return err 114 } 115 actionCtx := protocol.ActionCtx{} 116 actionCtx.Caller, err = address.FromString(_consortiumCommitteeContractCreator) 117 if err != nil { 118 return err 119 } 120 actionCtx.Nonce = _consortiumCommitteeContractNonce 121 actionCtx.ActionHash = _consortiumCommitteeContractHash 122 actionCtx.GasPrice = execution.GasPrice() 123 actionCtx.IntrinsicGas, err = execution.IntrinsicGas() 124 if err != nil { 125 return err 126 } 127 ctx = protocol.WithActionCtx(ctx, actionCtx) 128 ctx = protocol.WithBlockCtx(ctx, blkCtx) 129 getBlockTime := func(u uint64) (time.Time, error) { 130 // make sure the returned timestamp is after the current block time so that evm upgrades based on timestamp (Shanghai and onwards) are disabled 131 return blkCtx.BlockTimeStamp.Add(5 * time.Second), nil 132 } 133 ctx = evm.WithHelperCtx(ctx, evm.HelperContext{ 134 GetBlockHash: func(height uint64) (hash.Hash256, error) { 135 return hash.ZeroHash256, nil 136 }, 137 GetBlockTime: getBlockTime, 138 DepositGasFunc: func(context.Context, protocol.StateManager, address.Address, *big.Int, *big.Int) (*action.TransactionLog, error) { 139 return nil, nil 140 }, 141 Sgd: nil, 142 }) 143 144 // deploy consortiumCommittee contract 145 _, receipt, err := evm.ExecuteContract( 146 ctx, 147 sm, 148 execution, 149 ) 150 if err != nil { 151 return err 152 } 153 if receipt.Status != uint64(iotextypes.ReceiptStatus_Success) { 154 return errors.Errorf("error when deploying consortiumCommittee contract, status=%d", receipt.Status) 155 } 156 cc.contract = receipt.ContractAddress 157 158 ctx = evm.WithHelperCtx(ctx, evm.HelperContext{ 159 GetBlockHash: cc.getBlockHash, 160 GetBlockTime: getBlockTime, 161 }) 162 r := getContractReaderForGenesisStates(ctx, sm) 163 cands, err := cc.readDelegatesWithContractReader(ctx, r) 164 if err != nil { 165 return err 166 } 167 168 return setCandidates(ctx, sm, cc.indexer, cands, uint64(1)) 169 } 170 171 func (cc *consortiumCommittee) CreatePreStates(ctx context.Context, sm protocol.StateManager) error { 172 return nil 173 } 174 175 func (cc *consortiumCommittee) CreatePostSystemActions(ctx context.Context, sr protocol.StateReader) ([]action.Envelope, error) { 176 return createPostSystemActions(ctx, sr, cc) 177 } 178 179 func (cc *consortiumCommittee) Handle(ctx context.Context, act action.Action, sm protocol.StateManager) (*action.Receipt, error) { 180 return handle(ctx, act, sm, cc.indexer, cc.addr.String()) 181 } 182 183 func (cc *consortiumCommittee) Validate(ctx context.Context, act action.Action, sr protocol.StateReader) error { 184 return validate(ctx, sr, cc, act) 185 } 186 187 func (cc *consortiumCommittee) ReadState( 188 ctx context.Context, 189 sm protocol.StateReader, 190 method []byte, 191 args ...[]byte, 192 ) ([]byte, uint64, error) { 193 return nil, uint64(0), nil 194 } 195 196 // Register registers the protocol with a unique ID 197 func (cc *consortiumCommittee) Register(r *protocol.Registry) error { 198 return r.Register(_protocolID, cc) 199 } 200 201 // ForceRegister registers the protocol with a unique ID and force replacing the previous protocol if it exists 202 func (cc *consortiumCommittee) ForceRegister(r *protocol.Registry) error { 203 return r.ForceRegister(_protocolID, cc) 204 } 205 206 // Name returns the name of protocol 207 func (cc *consortiumCommittee) Name() string { 208 return _protocolID 209 } 210 211 func (cc *consortiumCommittee) CalculateCandidatesByHeight(ctx context.Context, _ protocol.StateReader, _ uint64) (state.CandidateList, error) { 212 return cc.readDelegates(ctx) 213 } 214 215 func (cc *consortiumCommittee) CalculateUnproductiveDelegates( 216 ctx context.Context, 217 sr protocol.StateReader, 218 ) ([]string, error) { 219 return nil, nil 220 } 221 222 func (cc *consortiumCommittee) Delegates(ctx context.Context, _ protocol.StateReader) (state.CandidateList, error) { 223 return cc.readDelegates(ctx) 224 } 225 226 func (cc *consortiumCommittee) NextDelegates(ctx context.Context, _ protocol.StateReader) (state.CandidateList, error) { 227 return cc.readDelegates(ctx) 228 } 229 230 func (cc *consortiumCommittee) Candidates(ctx context.Context, _ protocol.StateReader) (state.CandidateList, error) { 231 return cc.readDelegates(ctx) 232 } 233 234 func (cc *consortiumCommittee) NextCandidates(ctx context.Context, _ protocol.StateReader) (state.CandidateList, error) { 235 return cc.readDelegates(ctx) 236 } 237 238 func (cc *consortiumCommittee) readDelegates(ctx context.Context) (state.CandidateList, error) { 239 return cc.readDelegatesWithContractReader(ctx, cc.contractReader) 240 } 241 242 func (cc *consortiumCommittee) readDelegatesWithContractReader(ctx context.Context, r contractReader) (state.CandidateList, error) { 243 bcCtx := protocol.MustGetBlockchainCtx(ctx) 244 rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx)) 245 epochNum := rp.GetEpochNum(bcCtx.Tip.Height) 246 if cc.bufferEpochNum == epochNum && cc.bufferResult != nil { 247 return cc.bufferResult, nil 248 } 249 250 data, err := cc.abi.Pack("delegates") 251 if err != nil { 252 return nil, err 253 } 254 255 data, err = r.Read(ctx, cc.contract, data) 256 if err != nil { 257 return nil, err 258 } 259 ret, err := cc.abi.Unpack("delegates", data) 260 if err != nil { 261 return nil, err 262 } 263 res, err := toEtherAddressSlice(ret[0]) 264 if err != nil { 265 return nil, err 266 } 267 268 candidates := make([]*state.Candidate, 0, len(res)) 269 for _, ethAddr := range res { 270 pkhash, err := hexutil.Decode(ethAddr.String()) 271 if err != nil { 272 return nil, err 273 } 274 addr, err := address.FromBytes(pkhash) 275 if err != nil { 276 return nil, err 277 } 278 candidates = append(candidates, &state.Candidate{ 279 Address: addr.String(), 280 Votes: big.NewInt(100), 281 }) 282 } 283 cc.bufferEpochNum = epochNum 284 cc.bufferResult = candidates 285 return candidates, nil 286 } 287 288 func genContractReaderFromReadContract(r ReadContract, setting bool) contractReaderFunc { 289 return func(ctx context.Context, contract string, data []byte) ([]byte, error) { 290 return r(ctx, contract, data, setting) 291 } 292 } 293 294 func getContractReaderForGenesisStates(ctx context.Context, sm protocol.StateManager) contractReaderFunc { 295 return func(ctx context.Context, contract string, data []byte) ([]byte, error) { 296 gasLimit := uint64(10000000) 297 ex, err := action.NewExecution(contract, 1, big.NewInt(0), gasLimit, big.NewInt(0), data) 298 if err != nil { 299 return nil, err 300 } 301 302 addr, err := address.FromString(address.ZeroAddress) 303 if err != nil { 304 return nil, err 305 } 306 307 res, _, err := evm.SimulateExecution(ctx, sm, addr, ex) 308 309 return res, err 310 } 311 } 312 313 func toEtherAddressSlice(v interface{}) ([]common.Address, error) { 314 if addr, ok := v.([]common.Address); ok { 315 return addr, nil 316 } 317 return nil, ErrWrongData 318 }