package core import ( "context" "errors" "github.com/matchsystems/werr" "mic-wallet/common" "time" ) var ErrOutOfDateBlockId = errors.New("") var ErrBlockIdDoesntExist = errors.New("") type IBlocksCreator interface { Run(ctx context.Context) error WriteTx(opts *common.NewTxOpts) error // GetTx Searches for tx in every block GetTx(block int64, hash string) *common.NewTxOpts GetTxs() []*common.NewTxOpts } // BlocksCreator creates and rotates new Blocks and pushed them to the commiter type BlocksCreator struct { blocks map[int64]*Block txsLeftOver map[string]*common.NewTxOpts commiter IBlocksCommiter prevBlockHash []byte } func NewBlocksCreator(commiter IBlocksCommiter) *BlocksCreator { return &BlocksCreator{ blocks: make(map[int64]*Block), txsLeftOver: make(map[string]*common.NewTxOpts), commiter: commiter, } } func (bc *BlocksCreator) Run(ctx context.Context) (err error) { var blockID int64 var prevLeftOver map[string]*common.NewTxOpts for { blockID = common.GetBlockId(time.Now()) if oldBlock, ok := bc.blocks[blockID-common.BlockSecsDiff]; ok { // Commit and delete old block // we need to know previous hash for new block. // new transactions while oldBlock.Finish is executing will // end up in leftOver so they will be parts of the next block if bc.prevBlockHash, err = oldBlock.Finish(); err != nil { return nil } go bc.commiter.CommitBlock(ctx, oldBlock) // may take a while delete(bc.blocks, blockID-common.BlockSecsDiff) } // prevLeftOver becomes new block's transactions // so there is no "lost" transactions prevLeftOver = bc.txsLeftOver // and also now we need to make new leftOver bc.txsLeftOver = make(map[string]*common.NewTxOpts) bc.blocks[blockID] = NewBlock(blockID, bc.prevBlockHash, prevLeftOver, bc.txsLeftOver) // Waiting for the next block time.Sleep(time.Until(time.Unix(blockID+common.BlockSecsDiff, 0))) } } // WriteTx validates transaction and writes it into the current block // if transaction is valid func (bc *BlocksCreator) WriteTx(opts *common.NewTxOpts) error { // validating may take some time, so to don't write into if err := opts.Validate(); err != nil { return werr.Wrapf(err, "error validating transaction %s", opts.HashString()) } currentBlockID := common.GetBlockId(time.Now()) if currentBlockID < opts.BlockId { return werr.Wrapf(common.ErrEntityNotFound, "block id %d does not exist", currentBlockID) } else if currentBlockID > opts.BlockId { return werr.Wrapf(common.ErrEntityOutdated, "block id %d is out of date", currentBlockID) } block, ok := bc.blocks[currentBlockID] if !ok { return werr.Wrapf(common.ErrEntityNotFound, "block id %d does not exist", currentBlockID) } if err := block.AddTx(opts); err != nil { return werr.Wrapf(err, "error adding transaction to block %d", currentBlockID) } return nil }