aboutsummaryrefslogblamecommitdiffstats
path: root/block_manager.go
blob: b6c2cace7a5634172df48830cb9726d4e2a5592e (plain) (tree)
1
2
3
4
5
6
7
8
9
10


            
                

                                        

                  

 
                        
                                
 
                                   

                   


                                  

                                                                           
 



                                                                                


                                                
                 

 
                                                  





                                                      

 
                          
                                                            
              

                             


                                      



                                    
 
                 


                   
                                                                  
                           



                                                       





                                                                   




















                                                                                     




                                                                       
                  

 

















                                                                                                              



                                                      






                                                                          

                                                                                  
                                                                   
               



                                                                            
                                                            
                                                 








                                                                                                            
                         

                                                                           
                 

         
                                                                           
                                                            
                                                                                          

                                                             
 

                                              














                                                                            
                  

 
                                                                                                            




















                                                                                                                       
 
package main

import (
    "errors"
    "fmt"
    "github.com/ethereum/ethutil-go"
    "log"
    "math/big"
)

type BlockChain struct {
    LastBlock *ethutil.Block

    genesisBlock *ethutil.Block

    TD *big.Int
}

func NewBlockChain() *BlockChain {
    bc := &BlockChain{}
    bc.genesisBlock = ethutil.NewBlock(ethutil.Encode(ethutil.Genesis))

    // Set the last know difficulty (might be 0x0 as initial value, Genesis)
    bc.TD = new(big.Int)
    bc.TD.SetBytes(ethutil.Config.Db.LastKnownTD())

    // TODO get last block from the database
    //bc.LastBlock = bc.genesisBlock

    return bc
}

func (bc *BlockChain) HasBlock(hash string) bool {
    data, _ := ethutil.Config.Db.Get([]byte(hash))
    return len(data) != 0
}

func (bc *BlockChain) GenesisBlock() *ethutil.Block {
    return bc.genesisBlock
}

type BlockManager struct {
    // Ethereum virtual machine for processing contracts
    vm *Vm
    // The block chain :)
    bc *BlockChain
}

func NewBlockManager() *BlockManager {
    bm := &BlockManager{
        vm: NewVm(),
        bc: NewBlockChain(),
    }

    return bm
}

// Process a block.
func (bm *BlockManager) ProcessBlock(block *ethutil.Block) error {
    // Block validation
    if err := bm.ValidateBlock(block); err != nil {
        return err
    }

    // I'm not sure, but I don't know if there should be thrown
    // any errors at this time.
    if err := bm.AccumelateRewards(block); err != nil {
        return err
    }

    // Get the tx count. Used to create enough channels to 'join' the go routines
    txCount := len(block.Transactions())
    // Locking channel. When it has been fully buffered this method will return
    lockChan := make(chan bool, txCount)

    // Process each transaction/contract
    for _, tx := range block.Transactions() {
        // If there's no recipient, it's a contract
        if tx.IsContract() {
            go bm.ProcessContract(tx, block, lockChan)
        } else {
            // "finish" tx which isn't a contract
            lockChan <- true
        }
    }

    // Wait for all Tx to finish processing
    for i := 0; i < txCount; i++ {
        <-lockChan
    }

    if bm.CalculateTD(block) {
        ethutil.Config.Db.Put(block.Hash(), block.MarshalRlp())
        bm.bc.LastBlock = block
    }

    return nil
}

func (bm *BlockManager) CalculateTD(block *ethutil.Block) bool {
    uncleDiff := new(big.Int)
    for _, uncle := range block.Uncles {
        uncleDiff = uncleDiff.Add(uncleDiff, uncle.Difficulty)
    }

    // TD(genesis_block) = 0 and TD(B) = TD(B.parent) + sum(u.difficulty for u in B.uncles) + B.difficulty
    td := new(big.Int)
    td = td.Add(bm.bc.TD, uncleDiff)
    td = td.Add(td, block.Difficulty)

    // The new TD will only be accepted if the new difficulty is
    // is greater than the previous.
    if td.Cmp(bm.bc.TD) > 0 {
        bm.bc.LastBlock = block
        // Set the new total difficulty back to the block chain
        bm.bc.TD = td

        if Debug {
            log.Println("TD(block) =", td)
        }

        return true
    }

    return false
}

// Validates the current block. Returns an error if the block was invalid,
// an uncle or anything that isn't on the current block chain.
// Validation validates easy over difficult (dagger takes longer time = difficult)
func (bm *BlockManager) ValidateBlock(block *ethutil.Block) error {
    // TODO
    // 2. Check if the difficulty is correct

    // Check if we have the parent hash, if it isn't known we discard it
    // Reasons might be catching up or simply an invalid block
    if bm.bc.LastBlock != nil && block.PrevHash == "" &&
        !bm.bc.HasBlock(block.PrevHash) {
        return errors.New("Block's parent unknown")
    }

    // Check each uncle's previous hash. In order for it to be valid
    // is if it has the same block hash as the current
    for _, uncle := range block.Uncles {
        if uncle.PrevHash != block.PrevHash {
            if Debug {
                log.Printf("Uncle prvhash mismatch %x %x\n", block.PrevHash, uncle.PrevHash)
            }

            return errors.New("Mismatching Prvhash from uncle")
        }
    }

    // Verify the nonce of the block. Return an error if it's not valid
    if bm.bc.LastBlock != nil && block.PrevHash == "" &&
        !DaggerVerify(ethutil.BigD(block.Hash()), block.Difficulty, block.Nonce) {
        return errors.New("Block's nonce is invalid")
    }

    log.Println("Block validation PASSED")

    return nil
}

func (bm *BlockManager) AccumelateRewards(block *ethutil.Block) error {
    // Get the coinbase rlp data
    d := block.State().Get(block.Coinbase)

    ether := ethutil.NewEtherFromData([]byte(d))

    // Reward amount of ether to the coinbase address
    ether.AddFee(ethutil.CalculateBlockReward(block, len(block.Uncles)))
    block.State().Update(block.Coinbase, string(ether.MarshalRlp()))

    // TODO Reward each uncle

    return nil
}

func (bm *BlockManager) ProcessContract(tx *ethutil.Transaction, block *ethutil.Block, lockChan chan bool) {
    // Recovering function in case the VM had any errors
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from VM execution with err =", r)
            // Let the channel know where done even though it failed (so the execution may resume normally)
            lockChan <- true
        }
    }()

    // Process contract
    bm.vm.ProcContract(tx, block, func(opType OpType) bool {
        // TODO turn on once big ints are in place
        //if !block.PayFee(tx.Hash(), StepFee.Uint64()) {
        //  return false
        //}

        return true // Continue
    })

    // Broadcast we're done
    lockChan <- true
}