package ethchain import ( "bytes" "fmt" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/eth-go/ethwire" "math/big" "sync" "time" ) type BlockProcessor interface { ProcessBlock(block *Block) } type EthManager interface { StateManager() *StateManager BlockChain() *BlockChain TxPool() *TxPool Broadcast(msgType ethwire.MsgType, data []interface{}) Reactor() *ethutil.ReactorEngine } type StateManager struct { // Mutex for locking the block processor. Blocks can only be handled one at a time mutex sync.Mutex // Canonical block chain bc *BlockChain // States for addresses. You can watch any address // at any given time stateObjectCache *StateObjectCache // Stack for processing contracts stack *Stack // non-persistent key/value memory storage mem map[string]*big.Int Pow PoW Ethereum EthManager SecondaryBlockProcessor BlockProcessor // The managed states // Processor state. Anything processed will be applied to this // state procState *State // Comparative state it used for comparing and validating end // results compState *State // It's generally know that a map is faster for small lookups than arrays // we'll eventually have to make a decision if the map grows too large watchedAddresses map[string]bool } func NewStateManager(ethereum EthManager) *StateManager { sm := &StateManager{ stack: NewStack(), mem: make(map[string]*big.Int), Pow: &EasyPow{}, Ethereum: ethereum, stateObjectCache: NewStateObjectCache(), bc: ethereum.BlockChain(), watchedAddresses: make(map[string]bool), } sm.procState = ethereum.BlockChain().CurrentBlock.State() return sm } func (sm *StateManager) ProcState() *State { return sm.procState } // Watches any given address and puts it in the address state store func (sm *StateManager) WatchAddr(addr []byte) *CachedStateObject { //XXX account := sm.bc.CurrentBlock.state.GetAccount(addr) account := sm.procState.GetAccount(addr) return sm.stateObjectCache.Add(addr, account) } func (sm *StateManager) GetAddrState(addr []byte) *CachedStateObject { account := sm.stateObjectCache.Get(addr) if account == nil { a := sm.procState.GetAccount(addr) account = &CachedStateObject{Nonce: a.Nonce, Object: a} } return account } func (sm *StateManager) BlockChain() *BlockChain { return sm.bc } func (sm *StateManager) MakeContract(tx *Transaction) *StateObject { contract := MakeContract(tx, sm.procState) if contract != nil { sm.procState.states[string(tx.Hash()[12:])] = contract.state return contract } return nil } // Apply transactions uses the transaction passed to it and applies them onto // the current processing state. func (sm *StateManager) ApplyTransactions(block *Block, txs []*Transaction) { // Process each transaction/contract for _, tx := range txs { fmt.Printf("Processing Tx: %x\n", tx.Hash()) // If there's no recipient, it's a contract // Check if this is a contract creation traction and if so // create a contract of this tx. if tx.IsContract() { err := sm.Ethereum.TxPool().ProcessTransaction(tx, block, false) if err == nil { contract := sm.MakeContract(tx) if contract != nil { sm.EvalScript(contract.Init(), contract, tx, block) fmt.Printf("state root of contract %x\n", contract.State().Root()) } else { ethutil.Config.Log.Infoln("[STATE] Unable to create contract") } } else { ethutil.Config.Log.Infoln("[STATE] contract create:", err) } } else { err := sm.Ethereum.TxPool().ProcessTransaction(tx, block, false) contract := sm.procState.GetContract(tx.Recipient) if err == nil && len(contract.Script()) > 0 { sm.EvalScript(contract.Script(), contract, tx, block) } else if err != nil { ethutil.Config.Log.Infoln("[STATE] process:", err) } } } } // The prepare function, prepares the state manager for the next // "ProcessBlock" action. func (sm *StateManager) Prepare(processor *State, comparative *State) { sm.compState = comparative sm.procState = processor } // Default prepare function func (sm *StateManager) PrepareDefault(block *Block) { sm.Prepare(sm.BlockChain().CurrentBlock.State(), block.State()) } // Block processing and validating with a given (temporarily) state func (sm *StateManager) ProcessBlock(block *Block, dontReact bool) error { // Processing a blocks may never happen simultaneously sm.mutex.Lock() defer sm.mutex.Unlock() hash := block.Hash() if sm.bc.HasBlock(hash) { fmt.Println("[STATE] We already have this block, ignoring") return nil } // Defer the Undo on the Trie. If the block processing happened // we don't want to undo but since undo only happens on dirty // nodes this won't happen because Commit would have been called // before that. defer sm.bc.CurrentBlock.Undo() // 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 !sm.bc.HasBlock(block.PrevHash) && sm.bc.CurrentBlock != nil { return ParentError(block.PrevHash) } // Process the transactions on to current block sm.ApplyTransactions(sm.bc.CurrentBlock, block.Transactions()) // Block validation if err := sm.ValidateBlock(block); err != nil { fmt.Println("[SM] Error validating block:", err) return err } // I'm not sure, but I don't know if there should be thrown // any errors at this time. if err := sm.AccumelateRewards(block); err != nil { fmt.Println("[SM] Error accumulating reward", err) return err } if !sm.compState.Cmp(sm.procState) { return fmt.Errorf("Invalid merkle root. Expected %x, got %x", sm.compState.trie.Root, sm.procState.trie.Root) } // Calculate the new total difficulty and sync back to the db if sm.CalculateTD(block) { // Sync the current block's state to the database and cancelling out the deferred Undo sm.procState.Sync() // Add the block to the chain sm.bc.Add(block) // If there's a block processor present, pass in the block for further // processing if sm.SecondaryBlockProcessor != nil { sm.SecondaryBlockProcessor.ProcessBlock(block) } ethutil.Config.Log.Infof("[STATE] Added block #%d (%x)\n", block.BlockInfo().Number, block.Hash()) if dontReact == false { sm.Ethereum.Reactor().Post("newBlock", block) } } else { fmt.Println("total diff failed") } return nil } func (sm *StateManager) CalculateTD(block *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(sm.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(sm.bc.TD) > 0 { // Set the new total difficulty back to the block chain sm.bc.SetTotalDifficulty(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 (sm *StateManager) ValidateBlock(block *Block) error { // TODO // 2. Check if the difficulty is correct // Check each uncle's previous hash. In order for it to be valid // is if it has the same block hash as the current previousBlock := sm.bc.GetBlock(block.PrevHash) for _, uncle := range block.Uncles { if bytes.Compare(uncle.PrevHash, previousBlock.PrevHash) != 0 { return ValidationError("Mismatch uncle's previous hash. Expected %x, got %x", previousBlock.PrevHash, uncle.PrevHash) } } diff := block.Time - sm.bc.CurrentBlock.Time if diff < 0 { return ValidationError("Block timestamp less then prev block %v", diff) } // New blocks must be within the 15 minute range of the last block. if diff > int64(15*time.Minute) { return ValidationError("Block is too far in the future of last block (> 15 minutes)") } // Verify the nonce of the block. Return an error if it's not valid if !sm.Pow.Verify(block.HashNoNonce(), block.Difficulty, block.Nonce) { return ValidationError("Block's nonce is invalid (= %v)", ethutil.Hex(block.Nonce)) } return nil } func CalculateBlockReward(block *Block, uncleLength int) *big.Int { base := new(big.Int) for i := 0; i < uncleLength; i++ { base.Add(base, UncleInclusionReward) } return base.Add(base, BlockReward) } func CalculateUncleReward(block *Block) *big.Int { return UncleReward } func (sm *StateManager) AccumelateRewards(block *Block) error { // Get the account associated with the coinbase account := sm.procState.GetAccount(block.Coinbase) // Reward amount of ether to the coinbase address account.AddAmount(CalculateBlockReward(block, len(block.Uncles))) addr := make([]byte, len(block.Coinbase)) copy(addr, block.Coinbase) sm.procState.UpdateStateObject(account) for _, uncle := range block.Uncles { uncleAccount := sm.procState.GetAccount(uncle.Coinbase) uncleAccount.AddAmount(CalculateUncleReward(uncle)) sm.procState.UpdateStateObject(uncleAccount) } return nil } func (sm *StateManager) Stop() { sm.bc.Stop() } func (sm *StateManager) EvalScript(script []byte, object *StateObject, tx *Transaction, block *Block) { account := sm.procState.GetAccount(tx.Sender()) err := account.ConvertGas(tx.Gas, tx.GasPrice) if err != nil { ethutil.Config.Log.Debugln(err) return } closure := NewClosure(account, object, script, sm.procState, tx.Gas, tx.GasPrice, tx.Value) vm := NewVm(sm.procState, sm, RuntimeVars{ Origin: account.Address(), BlockNumber: block.BlockInfo().Number, PrevHash: block.PrevHash, Coinbase: block.Coinbase, Time: block.Time, Diff: block.Difficulty, //Price: tx.GasPrice, }) closure.Call(vm, tx.Data, nil) // Update the account (refunds) sm.procState.UpdateStateObject(account) sm.Changed(account) sm.procState.UpdateStateObject(object) sm.Changed(object) } // Watch a specific address func (sm *StateManager) Watch(addr []byte) { if !sm.watchedAddresses[string(addr)] { sm.watchedAddresses[string(addr)] = true } } // The following objects are used when changing a value and using the "watched" attribute // to determine whether the reactor should be used to notify any subscribers on the address func (sm *StateManager) Changed(stateObject *StateObject) { if sm.watchedAddresses[string(stateObject.Address())] { sm.Ethereum.Reactor().Post("addressChanged", stateObject) } }