github.com/iotexproject/iotex-core@v1.14.1-rc1/blockchain/blockchain.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 blockchain 7 8 import ( 9 "context" 10 "strconv" 11 "sync" 12 "sync/atomic" 13 "time" 14 15 "github.com/facebookgo/clock" 16 "github.com/iotexproject/go-pkgs/hash" 17 "github.com/iotexproject/iotex-address/address" 18 "github.com/pkg/errors" 19 "github.com/prometheus/client_golang/prometheus" 20 "go.uber.org/zap" 21 22 "github.com/iotexproject/iotex-core/action" 23 "github.com/iotexproject/iotex-core/action/protocol" 24 "github.com/iotexproject/iotex-core/blockchain/block" 25 "github.com/iotexproject/iotex-core/blockchain/blockdao" 26 "github.com/iotexproject/iotex-core/blockchain/filedao" 27 "github.com/iotexproject/iotex-core/blockchain/genesis" 28 "github.com/iotexproject/iotex-core/pkg/lifecycle" 29 "github.com/iotexproject/iotex-core/pkg/log" 30 "github.com/iotexproject/iotex-core/pkg/prometheustimer" 31 ) 32 33 // const 34 const ( 35 SigP256k1 = "secp256k1" 36 SigP256sm2 = "p256sm2" 37 ) 38 39 var ( 40 _blockMtc = prometheus.NewGaugeVec( 41 prometheus.GaugeOpts{ 42 Name: "iotex_block_metrics", 43 44 Help: "Block metrics.", 45 }, 46 []string{"type"}, 47 ) 48 // ErrInvalidTipHeight is the error returned when the block height is not valid 49 ErrInvalidTipHeight = errors.New("invalid tip height") 50 // ErrInvalidBlock is the error returned when the block is not valid 51 ErrInvalidBlock = errors.New("failed to validate the block") 52 // ErrActionNonce is the error when the nonce of the action is wrong 53 ErrActionNonce = errors.New("invalid action nonce") 54 // ErrInsufficientGas indicates the error of insufficient gas value for data storage 55 ErrInsufficientGas = errors.New("insufficient intrinsic gas value") 56 // ErrBalance indicates the error of balance 57 ErrBalance = errors.New("invalid balance") 58 ) 59 60 func init() { 61 prometheus.MustRegister(_blockMtc) 62 } 63 64 type ( 65 // Blockchain represents the blockchain data structure and hosts the APIs to access it 66 Blockchain interface { 67 lifecycle.StartStopper 68 69 // For exposing blockchain states 70 // BlockHeaderByHeight return block header by height 71 BlockHeaderByHeight(height uint64) (*block.Header, error) 72 // BlockFooterByHeight return block footer by height 73 BlockFooterByHeight(height uint64) (*block.Footer, error) 74 // ChainID returns the chain ID 75 ChainID() uint32 76 // EvmNetworkID returns the evm network ID 77 EvmNetworkID() uint32 78 // ChainAddress returns chain address on parent chain, the root chain return empty. 79 ChainAddress() string 80 // TipHash returns tip block's hash 81 TipHash() hash.Hash256 82 // TipHeight returns tip block's height 83 TipHeight() uint64 84 // Genesis returns the genesis 85 Genesis() genesis.Genesis 86 // Context returns current context 87 Context(context.Context) (context.Context, error) 88 89 // For block operations 90 // MintNewBlock creates a new block with given actions 91 // Note: the coinbase transfer will be added to the given transfers when minting a new block 92 MintNewBlock(timestamp time.Time) (*block.Block, error) 93 // CommitBlock validates and appends a block to the chain 94 CommitBlock(blk *block.Block) error 95 // ValidateBlock validates a new block before adding it to the blockchain 96 ValidateBlock(blk *block.Block) error 97 98 // AddSubscriber make you listen to every single produced block 99 AddSubscriber(BlockCreationSubscriber) error 100 101 // RemoveSubscriber make you listen to every single produced block 102 RemoveSubscriber(BlockCreationSubscriber) error 103 } 104 105 // BlockBuilderFactory is the factory interface of block builder 106 BlockBuilderFactory interface { 107 // NewBlockBuilder creates block builder 108 NewBlockBuilder(context.Context, func(action.Envelope) (*action.SealedEnvelope, error)) (*block.Builder, error) 109 } 110 111 // blockchain implements the Blockchain interface 112 blockchain struct { 113 mu sync.RWMutex // mutex to protect utk, tipHeight and tipHash 114 dao blockdao.BlockDAO 115 config Config 116 genesis genesis.Genesis 117 blockValidator block.Validator 118 lifecycle lifecycle.Lifecycle 119 clk clock.Clock 120 pubSubManager PubSubManager 121 timerFactory *prometheustimer.TimerFactory 122 123 // used by account-based model 124 bbf BlockBuilderFactory 125 } 126 ) 127 128 // Productivity returns the map of the number of blocks produced per delegate in given epoch 129 func Productivity(bc Blockchain, startHeight uint64, endHeight uint64) (map[string]uint64, error) { 130 stats := make(map[string]uint64) 131 for i := startHeight; i <= endHeight; i++ { 132 header, err := bc.BlockHeaderByHeight(i) 133 if err != nil { 134 return nil, err 135 } 136 producer := header.ProducerAddress() 137 stats[producer]++ 138 } 139 140 return stats, nil 141 } 142 143 // Option sets blockchain construction parameter 144 type Option func(*blockchain) error 145 146 // BlockValidatorOption sets block validator 147 func BlockValidatorOption(blockValidator block.Validator) Option { 148 return func(bc *blockchain) error { 149 bc.blockValidator = blockValidator 150 return nil 151 } 152 } 153 154 // ClockOption overrides the default clock 155 func ClockOption(clk clock.Clock) Option { 156 return func(bc *blockchain) error { 157 bc.clk = clk 158 return nil 159 } 160 } 161 162 // NewBlockchain creates a new blockchain and DB instance 163 func NewBlockchain(cfg Config, g genesis.Genesis, dao blockdao.BlockDAO, bbf BlockBuilderFactory, opts ...Option) Blockchain { 164 // create the Blockchain 165 chain := &blockchain{ 166 config: cfg, 167 genesis: g, 168 dao: dao, 169 bbf: bbf, 170 clk: clock.New(), 171 pubSubManager: NewPubSub(cfg.StreamingBlockBufferSize), 172 } 173 for _, opt := range opts { 174 if err := opt(chain); err != nil { 175 log.S().Panicf("Failed to execute blockchain creation option %p: %v", opt, err) 176 } 177 } 178 timerFactory, err := prometheustimer.New( 179 "iotex_blockchain_perf", 180 "Performance of blockchain module", 181 []string{"topic", "chainID"}, 182 []string{"default", strconv.FormatUint(uint64(cfg.ID), 10)}, 183 ) 184 if err != nil { 185 log.L().Panic("Failed to generate prometheus timer factory.", zap.Error(err)) 186 } 187 chain.timerFactory = timerFactory 188 if chain.dao == nil { 189 log.L().Panic("blockdao is nil") 190 } 191 chain.lifecycle.Add(chain.dao) 192 chain.lifecycle.Add(chain.pubSubManager) 193 194 return chain 195 } 196 197 func (bc *blockchain) ChainID() uint32 { 198 return atomic.LoadUint32(&bc.config.ID) 199 } 200 201 func (bc *blockchain) EvmNetworkID() uint32 { 202 return atomic.LoadUint32(&bc.config.EVMNetworkID) 203 } 204 205 func (bc *blockchain) ChainAddress() string { 206 return bc.config.Address 207 } 208 209 // Start starts the blockchain 210 func (bc *blockchain) Start(ctx context.Context) error { 211 bc.mu.Lock() 212 defer bc.mu.Unlock() 213 214 // pass registry to be used by state factory's initialization 215 ctx, err := bc.context(ctx, false) 216 if err != nil { 217 return err 218 } 219 return bc.lifecycle.OnStart(ctx) 220 } 221 222 // Stop stops the blockchain. 223 func (bc *blockchain) Stop(ctx context.Context) error { 224 bc.mu.Lock() 225 defer bc.mu.Unlock() 226 return bc.lifecycle.OnStop(ctx) 227 } 228 229 func (bc *blockchain) BlockHeaderByHeight(height uint64) (*block.Header, error) { 230 return bc.dao.HeaderByHeight(height) 231 } 232 233 func (bc *blockchain) BlockFooterByHeight(height uint64) (*block.Footer, error) { 234 return bc.dao.FooterByHeight(height) 235 } 236 237 // TipHash returns tip block's hash 238 func (bc *blockchain) TipHash() hash.Hash256 { 239 tipHeight, err := bc.dao.Height() 240 if err != nil { 241 return hash.ZeroHash256 242 } 243 tipHash, err := bc.dao.GetBlockHash(tipHeight) 244 if err != nil { 245 return hash.ZeroHash256 246 } 247 return tipHash 248 } 249 250 // TipHeight returns tip block's height 251 func (bc *blockchain) TipHeight() uint64 { 252 tipHeight, err := bc.dao.Height() 253 if err != nil { 254 log.L().Panic("failed to get tip height", zap.Error(err)) 255 } 256 return tipHeight 257 } 258 259 // ValidateBlock validates a new block before adding it to the blockchain 260 func (bc *blockchain) ValidateBlock(blk *block.Block) error { 261 bc.mu.RLock() 262 defer bc.mu.RUnlock() 263 timer := bc.timerFactory.NewTimer("ValidateBlock") 264 defer timer.End() 265 if blk == nil { 266 return ErrInvalidBlock 267 } 268 tip, err := bc.tipInfo() 269 if err != nil { 270 return err 271 } 272 // verify new block has height incremented by 1 273 if blk.Height() != 0 && blk.Height() != tip.Height+1 { 274 return errors.Wrapf( 275 ErrInvalidTipHeight, 276 "wrong block height %d, expecting %d", 277 blk.Height(), 278 tip.Height+1, 279 ) 280 } 281 // verify new block has correctly linked to current tip 282 if blk.PrevHash() != tip.Hash { 283 blk.HeaderLogger(log.L()).Error("Previous block hash doesn't match.", 284 log.Hex("expectedBlockHash", tip.Hash[:])) 285 return errors.Wrapf( 286 ErrInvalidBlock, 287 "wrong prev hash %x, expecting %x", 288 blk.PrevHash(), 289 tip.Hash, 290 ) 291 } 292 293 if !blk.Header.VerifySignature() { 294 return errors.Errorf("failed to verify block's signature with public key: %x", blk.PublicKey()) 295 } 296 if err := blk.VerifyTxRoot(); err != nil { 297 return err 298 } 299 300 producerAddr := blk.PublicKey().Address() 301 if producerAddr == nil { 302 return errors.New("failed to get address") 303 } 304 ctx, err := bc.context(context.Background(), true) 305 if err != nil { 306 return err 307 } 308 ctx = protocol.WithBlockCtx(ctx, 309 protocol.BlockCtx{ 310 BlockHeight: blk.Height(), 311 BlockTimeStamp: blk.Timestamp(), 312 GasLimit: bc.genesis.BlockGasLimitByHeight(blk.Height()), 313 Producer: producerAddr, 314 }, 315 ) 316 ctx = protocol.WithFeatureCtx(ctx) 317 if bc.blockValidator == nil { 318 return nil 319 } 320 321 return bc.blockValidator.Validate(ctx, blk) 322 } 323 324 func (bc *blockchain) Context(ctx context.Context) (context.Context, error) { 325 bc.mu.RLock() 326 defer bc.mu.RUnlock() 327 328 return bc.context(ctx, true) 329 } 330 331 func (bc *blockchain) contextWithBlock(ctx context.Context, producer address.Address, height uint64, timestamp time.Time) context.Context { 332 return protocol.WithBlockCtx( 333 ctx, 334 protocol.BlockCtx{ 335 BlockHeight: height, 336 BlockTimeStamp: timestamp, 337 Producer: producer, 338 GasLimit: bc.genesis.BlockGasLimitByHeight(height), 339 }) 340 } 341 342 func (bc *blockchain) context(ctx context.Context, tipInfoFlag bool) (context.Context, error) { 343 var tip protocol.TipInfo 344 if tipInfoFlag { 345 if tipInfoValue, err := bc.tipInfo(); err == nil { 346 tip = *tipInfoValue 347 } else { 348 return nil, err 349 } 350 } 351 352 ctx = genesis.WithGenesisContext( 353 protocol.WithBlockchainCtx( 354 ctx, 355 protocol.BlockchainCtx{ 356 Tip: tip, 357 ChainID: bc.ChainID(), 358 EvmNetworkID: bc.EvmNetworkID(), 359 }, 360 ), 361 bc.genesis, 362 ) 363 return protocol.WithFeatureWithHeightCtx(ctx), nil 364 } 365 366 func (bc *blockchain) MintNewBlock(timestamp time.Time) (*block.Block, error) { 367 bc.mu.RLock() 368 defer bc.mu.RUnlock() 369 mintNewBlockTimer := bc.timerFactory.NewTimer("MintNewBlock") 370 defer mintNewBlockTimer.End() 371 tipHeight, err := bc.dao.Height() 372 if err != nil { 373 return nil, err 374 } 375 newblockHeight := tipHeight + 1 376 ctx, err := bc.context(context.Background(), true) 377 if err != nil { 378 return nil, err 379 } 380 ctx = bc.contextWithBlock(ctx, bc.config.ProducerAddress(), newblockHeight, timestamp) 381 ctx = protocol.WithFeatureCtx(ctx) 382 // run execution and update state trie root hash 383 minterPrivateKey := bc.config.ProducerPrivateKey() 384 blockBuilder, err := bc.bbf.NewBlockBuilder( 385 ctx, 386 func(elp action.Envelope) (*action.SealedEnvelope, error) { 387 return action.Sign(elp, minterPrivateKey) 388 }, 389 ) 390 if err != nil { 391 return nil, errors.Wrapf(err, "failed to create block builder at new block height %d", newblockHeight) 392 } 393 blk, err := blockBuilder.SignAndBuild(minterPrivateKey) 394 if err != nil { 395 return nil, errors.Wrapf(err, "failed to create block") 396 } 397 398 return &blk, nil 399 } 400 401 // CommitBlock validates and appends a block to the chain 402 func (bc *blockchain) CommitBlock(blk *block.Block) error { 403 bc.mu.Lock() 404 defer bc.mu.Unlock() 405 timer := bc.timerFactory.NewTimer("CommitBlock") 406 defer timer.End() 407 return bc.commitBlock(blk) 408 } 409 410 func (bc *blockchain) AddSubscriber(s BlockCreationSubscriber) error { 411 log.L().Info("Add a subscriber.") 412 if s == nil { 413 return errors.New("subscriber could not be nil") 414 } 415 416 return bc.pubSubManager.AddBlockListener(s) 417 } 418 419 func (bc *blockchain) RemoveSubscriber(s BlockCreationSubscriber) error { 420 return bc.pubSubManager.RemoveBlockListener(s) 421 } 422 423 //====================================== 424 // internal functions 425 //===================================== 426 427 func (bc *blockchain) Genesis() genesis.Genesis { 428 return bc.genesis 429 } 430 431 //====================================== 432 // private functions 433 //===================================== 434 435 func (bc *blockchain) tipInfo() (*protocol.TipInfo, error) { 436 tipHeight, err := bc.dao.Height() 437 if err != nil { 438 return nil, err 439 } 440 if tipHeight == 0 { 441 return &protocol.TipInfo{ 442 Height: 0, 443 Hash: bc.genesis.Hash(), 444 Timestamp: time.Unix(bc.genesis.Timestamp, 0), 445 }, nil 446 } 447 header, err := bc.dao.HeaderByHeight(tipHeight) 448 if err != nil { 449 return nil, err 450 } 451 452 return &protocol.TipInfo{ 453 Height: tipHeight, 454 Hash: header.HashBlock(), 455 Timestamp: header.Timestamp(), 456 }, nil 457 } 458 459 // commitBlock commits a block to the chain 460 func (bc *blockchain) commitBlock(blk *block.Block) error { 461 ctx, err := bc.context(context.Background(), true) 462 if err != nil { 463 return err 464 } 465 466 // write block into DB 467 putTimer := bc.timerFactory.NewTimer("putBlock") 468 err = bc.dao.PutBlock(ctx, blk) 469 putTimer.End() 470 switch { 471 case errors.Cause(err) == filedao.ErrAlreadyExist: 472 return nil 473 case err != nil: 474 return err 475 } 476 blkHash := blk.HashBlock() 477 if blk.Height()%100 == 0 { 478 blk.HeaderLogger(log.L()).Info("Committed a block.", log.Hex("tipHash", blkHash[:])) 479 } 480 _blockMtc.WithLabelValues("numActions").Set(float64(len(blk.Actions))) 481 // emit block to all block subscribers 482 bc.emitToSubscribers(blk) 483 return nil 484 } 485 486 func (bc *blockchain) emitToSubscribers(blk *block.Block) { 487 if bc.pubSubManager == nil { 488 return 489 } 490 bc.pubSubManager.SendBlockToSubscribers(blk) 491 }