diff options
author | Jeffrey Wilcke <obscuren@users.noreply.github.com> | 2014-10-23 22:46:18 +0800 |
---|---|---|
committer | Jeffrey Wilcke <obscuren@users.noreply.github.com> | 2014-10-23 22:46:18 +0800 |
commit | 119c5b40a7ed1aea1c871c0cb56956b8ef9303d9 (patch) | |
tree | b021423da04c9ff319a77549b473b6bad4930dd4 /ethchain | |
parent | 50fd46924900869e7210217c6a07979b544991c8 (diff) | |
parent | 184055b3e2995894ccaba364484223e488730627 (diff) | |
download | go-tangerine-119c5b40a7ed1aea1c871c0cb56956b8ef9303d9.tar go-tangerine-119c5b40a7ed1aea1c871c0cb56956b8ef9303d9.tar.gz go-tangerine-119c5b40a7ed1aea1c871c0cb56956b8ef9303d9.tar.bz2 go-tangerine-119c5b40a7ed1aea1c871c0cb56956b8ef9303d9.tar.lz go-tangerine-119c5b40a7ed1aea1c871c0cb56956b8ef9303d9.tar.xz go-tangerine-119c5b40a7ed1aea1c871c0cb56956b8ef9303d9.tar.zst go-tangerine-119c5b40a7ed1aea1c871c0cb56956b8ef9303d9.zip |
Merge pull request #150 from fjl/develop
Merge eth-go repo into go-ethereum
Diffstat (limited to 'ethchain')
-rw-r--r-- | ethchain/.gitignore | 12 | ||||
-rw-r--r-- | ethchain/asm.go | 45 | ||||
-rw-r--r-- | ethchain/block.go | 439 | ||||
-rw-r--r-- | ethchain/bloom.go | 47 | ||||
-rw-r--r-- | ethchain/bloom_test.go | 20 | ||||
-rw-r--r-- | ethchain/chain_manager.go | 291 | ||||
-rw-r--r-- | ethchain/chain_manager_test.go | 1 | ||||
-rw-r--r-- | ethchain/dagger.go | 239 | ||||
-rw-r--r-- | ethchain/dagger_test.go | 19 | ||||
-rw-r--r-- | ethchain/error.go | 116 | ||||
-rw-r--r-- | ethchain/events.go | 10 | ||||
-rw-r--r-- | ethchain/fees.go | 7 | ||||
-rw-r--r-- | ethchain/filter.go | 206 | ||||
-rw-r--r-- | ethchain/filter_test.go | 7 | ||||
-rw-r--r-- | ethchain/genesis.go | 48 | ||||
-rw-r--r-- | ethchain/helper_test.go | 92 | ||||
-rw-r--r-- | ethchain/state_manager.go | 446 | ||||
-rw-r--r-- | ethchain/state_transition.go | 276 | ||||
-rw-r--r-- | ethchain/transaction.go | 257 | ||||
-rw-r--r-- | ethchain/transaction_pool.go | 224 | ||||
-rw-r--r-- | ethchain/transaction_test.go | 1 | ||||
-rw-r--r-- | ethchain/types.go | 312 | ||||
-rw-r--r-- | ethchain/vm_env.go | 36 |
23 files changed, 3151 insertions, 0 deletions
diff --git a/ethchain/.gitignore b/ethchain/.gitignore new file mode 100644 index 000000000..f725d58d1 --- /dev/null +++ b/ethchain/.gitignore @@ -0,0 +1,12 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile ~/.gitignore_global + +/tmp +*/**/*un~ +*un~ +.DS_Store +*/**/.DS_Store + diff --git a/ethchain/asm.go b/ethchain/asm.go new file mode 100644 index 000000000..57bb2fcf9 --- /dev/null +++ b/ethchain/asm.go @@ -0,0 +1,45 @@ +package ethchain + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/ethutil" +) + +func Disassemble(script []byte) (asm []string) { + pc := new(big.Int) + for { + if pc.Cmp(big.NewInt(int64(len(script)))) >= 0 { + return + } + + // Get the memory location of pc + val := script[pc.Int64()] + // Get the opcode (it must be an opcode!) + op := OpCode(val) + + asm = append(asm, fmt.Sprintf("%04v: %v", pc, op)) + + switch op { + case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32: + pc.Add(pc, ethutil.Big1) + a := int64(op) - int64(PUSH1) + 1 + if int(pc.Int64()+a) > len(script) { + return + } + + data := script[pc.Int64() : pc.Int64()+a] + if len(data) == 0 { + data = []byte{0} + } + asm = append(asm, fmt.Sprintf("%04v: 0x%x", pc, data)) + + pc.Add(pc, big.NewInt(a-1)) + } + + pc.Add(pc, ethutil.Big1) + } + + return asm +} diff --git a/ethchain/block.go b/ethchain/block.go new file mode 100644 index 000000000..b98d806d8 --- /dev/null +++ b/ethchain/block.go @@ -0,0 +1,439 @@ +package ethchain + +import ( + "bytes" + "fmt" + "math/big" + "sort" + _ "strconv" + "time" + + "github.com/ethereum/go-ethereum/ethcrypto" + "github.com/ethereum/go-ethereum/ethstate" + "github.com/ethereum/go-ethereum/ethtrie" + "github.com/ethereum/go-ethereum/ethutil" +) + +type BlockInfo struct { + Number uint64 + Hash []byte + Parent []byte + TD *big.Int +} + +func (bi *BlockInfo) RlpDecode(data []byte) { + decoder := ethutil.NewValueFromBytes(data) + + bi.Number = decoder.Get(0).Uint() + bi.Hash = decoder.Get(1).Bytes() + bi.Parent = decoder.Get(2).Bytes() + bi.TD = decoder.Get(3).BigInt() +} + +func (bi *BlockInfo) RlpEncode() []byte { + return ethutil.Encode([]interface{}{bi.Number, bi.Hash, bi.Parent, bi.TD}) +} + +type Blocks []*Block + +func (self Blocks) AsSet() ethutil.UniqueSet { + set := make(ethutil.UniqueSet) + for _, block := range self { + set.Insert(block.Hash()) + } + + return set +} + +type BlockBy func(b1, b2 *Block) bool + +func (self BlockBy) Sort(blocks Blocks) { + bs := blockSorter{ + blocks: blocks, + by: self, + } + sort.Sort(bs) +} + +type blockSorter struct { + blocks Blocks + by func(b1, b2 *Block) bool +} + +func (self blockSorter) Len() int { return len(self.blocks) } +func (self blockSorter) Swap(i, j int) { + self.blocks[i], self.blocks[j] = self.blocks[j], self.blocks[i] +} +func (self blockSorter) Less(i, j int) bool { return self.by(self.blocks[i], self.blocks[j]) } + +func Number(b1, b2 *Block) bool { return b1.Number.Cmp(b2.Number) < 0 } + +type Block struct { + // Hash to the previous block + PrevHash ethutil.Bytes + // Uncles of this block + Uncles Blocks + UncleSha []byte + // The coin base address + Coinbase []byte + // Block Trie state + //state *ethutil.Trie + state *ethstate.State + // Difficulty for the current block + Difficulty *big.Int + // Creation time + Time int64 + // The block number + Number *big.Int + // Minimum Gas Price + MinGasPrice *big.Int + // Gas limit + GasLimit *big.Int + // Gas used + GasUsed *big.Int + // Extra data + Extra string + // Block Nonce for verification + Nonce ethutil.Bytes + // List of transactions and/or contracts + transactions []*Transaction + receipts []*Receipt + TxSha []byte +} + +func NewBlockFromBytes(raw []byte) *Block { + block := &Block{} + block.RlpDecode(raw) + + return block +} + +// New block takes a raw encoded string +func NewBlockFromRlpValue(rlpValue *ethutil.Value) *Block { + block := &Block{} + block.RlpValueDecode(rlpValue) + + return block +} + +func CreateBlock(root interface{}, + prevHash []byte, + base []byte, + Difficulty *big.Int, + Nonce []byte, + extra string) *Block { + + block := &Block{ + PrevHash: prevHash, + Coinbase: base, + Difficulty: Difficulty, + Nonce: Nonce, + Time: time.Now().Unix(), + Extra: extra, + UncleSha: nil, + GasUsed: new(big.Int), + MinGasPrice: new(big.Int), + GasLimit: new(big.Int), + } + block.SetUncles([]*Block{}) + + block.state = ethstate.New(ethtrie.New(ethutil.Config.Db, root)) + + return block +} + +// Returns a hash of the block +func (block *Block) Hash() ethutil.Bytes { + return ethcrypto.Sha3(ethutil.NewValue(block.header()).Encode()) + //return ethcrypto.Sha3(block.Value().Encode()) +} + +func (block *Block) HashNoNonce() []byte { + return ethcrypto.Sha3(ethutil.Encode([]interface{}{block.PrevHash, + block.UncleSha, block.Coinbase, block.state.Trie.Root, + block.TxSha, block.Difficulty, block.Number, block.MinGasPrice, + block.GasLimit, block.GasUsed, block.Time, block.Extra})) +} + +func (block *Block) State() *ethstate.State { + return block.state +} + +func (block *Block) Transactions() []*Transaction { + return block.transactions +} + +func (block *Block) CalcGasLimit(parent *Block) *big.Int { + if block.Number.Cmp(big.NewInt(0)) == 0 { + return ethutil.BigPow(10, 6) + } + + // ((1024-1) * parent.gasLimit + (gasUsed * 6 / 5)) / 1024 + + previous := new(big.Int).Mul(big.NewInt(1024-1), parent.GasLimit) + current := new(big.Rat).Mul(new(big.Rat).SetInt(parent.GasUsed), big.NewRat(6, 5)) + curInt := new(big.Int).Div(current.Num(), current.Denom()) + + result := new(big.Int).Add(previous, curInt) + result.Div(result, big.NewInt(1024)) + + min := big.NewInt(125000) + + return ethutil.BigMax(min, result) +} + +func (block *Block) BlockInfo() BlockInfo { + bi := BlockInfo{} + data, _ := ethutil.Config.Db.Get(append(block.Hash(), []byte("Info")...)) + bi.RlpDecode(data) + + return bi +} + +func (self *Block) GetTransaction(hash []byte) *Transaction { + for _, receipt := range self.receipts { + if bytes.Compare(receipt.Tx.Hash(), hash) == 0 { + return receipt.Tx + } + } + + return nil +} + +// Sync the block's state and contract respectively +func (block *Block) Sync() { + block.state.Sync() +} + +func (block *Block) Undo() { + // Sync the block state itself + block.state.Reset() +} + +/////// Block Encoding +func (block *Block) rlpReceipts() interface{} { + // Marshal the transactions of this block + encR := make([]interface{}, len(block.receipts)) + for i, r := range block.receipts { + // Cast it to a string (safe) + encR[i] = r.RlpData() + } + + return encR +} + +func (block *Block) rlpUncles() interface{} { + // Marshal the transactions of this block + uncles := make([]interface{}, len(block.Uncles)) + for i, uncle := range block.Uncles { + // Cast it to a string (safe) + uncles[i] = uncle.header() + } + + return uncles +} + +func (block *Block) SetUncles(uncles []*Block) { + block.Uncles = uncles + + // Sha of the concatenated uncles + if len(uncles) > 0 { + block.UncleSha = ethcrypto.Sha3(ethutil.Encode(block.rlpUncles())) + } +} + +func (self *Block) SetReceipts(receipts []*Receipt, txs []*Transaction) { + self.receipts = receipts + self.setTransactions(txs) +} + +func (block *Block) setTransactions(txs []*Transaction) { + block.transactions = txs +} + +func CreateTxSha(receipts Receipts) (sha []byte) { + trie := ethtrie.New(ethutil.Config.Db, "") + for i, receipt := range receipts { + trie.Update(string(ethutil.NewValue(i).Encode()), string(ethutil.NewValue(receipt.RlpData()).Encode())) + } + + switch trie.Root.(type) { + case string: + sha = []byte(trie.Root.(string)) + case []byte: + sha = trie.Root.([]byte) + default: + panic(fmt.Sprintf("invalid root type %T", trie.Root)) + } + + return sha +} + +func (self *Block) SetTxHash(receipts Receipts) { + self.TxSha = CreateTxSha(receipts) +} + +func (block *Block) Value() *ethutil.Value { + return ethutil.NewValue([]interface{}{block.header(), block.rlpReceipts(), block.rlpUncles()}) +} + +func (block *Block) RlpEncode() []byte { + // Encode a slice interface which contains the header and the list of + // transactions. + return block.Value().Encode() +} + +func (block *Block) RlpDecode(data []byte) { + rlpValue := ethutil.NewValueFromBytes(data) + block.RlpValueDecode(rlpValue) +} + +func (block *Block) RlpValueDecode(decoder *ethutil.Value) { + header := decoder.Get(0) + + block.PrevHash = header.Get(0).Bytes() + block.UncleSha = header.Get(1).Bytes() + block.Coinbase = header.Get(2).Bytes() + block.state = ethstate.New(ethtrie.New(ethutil.Config.Db, header.Get(3).Val)) + block.TxSha = header.Get(4).Bytes() + block.Difficulty = header.Get(5).BigInt() + block.Number = header.Get(6).BigInt() + //fmt.Printf("#%v : %x\n", block.Number, block.Coinbase) + block.MinGasPrice = header.Get(7).BigInt() + block.GasLimit = header.Get(8).BigInt() + block.GasUsed = header.Get(9).BigInt() + block.Time = int64(header.Get(10).BigInt().Uint64()) + block.Extra = header.Get(11).Str() + block.Nonce = header.Get(12).Bytes() + + // Tx list might be empty if this is an uncle. Uncles only have their + // header set. + if decoder.Get(1).IsNil() == false { // Yes explicitness + receipts := decoder.Get(1) + block.transactions = make([]*Transaction, receipts.Len()) + block.receipts = make([]*Receipt, receipts.Len()) + for i := 0; i < receipts.Len(); i++ { + receipt := NewRecieptFromValue(receipts.Get(i)) + block.transactions[i] = receipt.Tx + block.receipts[i] = receipt + } + + } + + if decoder.Get(2).IsNil() == false { // Yes explicitness + uncles := decoder.Get(2) + block.Uncles = make([]*Block, uncles.Len()) + for i := 0; i < uncles.Len(); i++ { + block.Uncles[i] = NewUncleBlockFromValue(uncles.Get(i)) + } + } + +} + +func NewUncleBlockFromValue(header *ethutil.Value) *Block { + block := &Block{} + + block.PrevHash = header.Get(0).Bytes() + block.UncleSha = header.Get(1).Bytes() + block.Coinbase = header.Get(2).Bytes() + block.state = ethstate.New(ethtrie.New(ethutil.Config.Db, header.Get(3).Val)) + block.TxSha = header.Get(4).Bytes() + block.Difficulty = header.Get(5).BigInt() + block.Number = header.Get(6).BigInt() + block.MinGasPrice = header.Get(7).BigInt() + block.GasLimit = header.Get(8).BigInt() + block.GasUsed = header.Get(9).BigInt() + block.Time = int64(header.Get(10).BigInt().Uint64()) + block.Extra = header.Get(11).Str() + block.Nonce = header.Get(12).Bytes() + + return block +} + +func (block *Block) Trie() *ethtrie.Trie { + return block.state.Trie +} + +func (block *Block) GetRoot() interface{} { + return block.state.Trie.Root +} + +func (block *Block) Diff() *big.Int { + return block.Difficulty +} + +func (self *Block) Receipts() []*Receipt { + return self.receipts +} + +func (block *Block) header() []interface{} { + return []interface{}{ + // Sha of the previous block + block.PrevHash, + // Sha of uncles + block.UncleSha, + // Coinbase address + block.Coinbase, + // root state + block.state.Trie.Root, + // Sha of tx + block.TxSha, + // Current block Difficulty + block.Difficulty, + // The block number + block.Number, + // Block minimum gas price + block.MinGasPrice, + // Block upper gas bound + block.GasLimit, + // Block gas used + block.GasUsed, + // Time the block was found? + block.Time, + // Extra data + block.Extra, + // Block's Nonce for validation + block.Nonce, + } +} + +func (block *Block) String() string { + return fmt.Sprintf(` + BLOCK(%x): Size: %v + PrevHash: %x + UncleSha: %x + Coinbase: %x + Root: %x + TxSha: %x + Difficulty: %v + Number: %v + MinGas: %v + MaxLimit: %v + GasUsed: %v + Time: %v + Extra: %v + Nonce: %x + NumTx: %v +`, + block.Hash(), + block.Size(), + block.PrevHash, + block.UncleSha, + block.Coinbase, + block.state.Trie.Root, + block.TxSha, + block.Difficulty, + block.Number, + block.MinGasPrice, + block.GasLimit, + block.GasUsed, + block.Time, + block.Extra, + block.Nonce, + len(block.transactions), + ) +} + +func (self *Block) Size() ethutil.StorageSize { + return ethutil.StorageSize(len(self.RlpEncode())) +} diff --git a/ethchain/bloom.go b/ethchain/bloom.go new file mode 100644 index 000000000..5317ca0b1 --- /dev/null +++ b/ethchain/bloom.go @@ -0,0 +1,47 @@ +package ethchain + +type BloomFilter struct { + bin []byte +} + +func NewBloomFilter(bin []byte) *BloomFilter { + if bin == nil { + bin = make([]byte, 256) + } + + return &BloomFilter{ + bin: bin, + } +} + +func (self *BloomFilter) Set(addr []byte) { + if len(addr) < 8 { + chainlogger.Warnf("err: bloom set to small: %x\n", addr) + + return + } + + for _, i := range addr[len(addr)-8:] { + self.bin[i] = 1 + } +} + +func (self *BloomFilter) Search(addr []byte) bool { + if len(addr) < 8 { + chainlogger.Warnf("err: bloom search to small: %x\n", addr) + + return false + } + + for _, i := range addr[len(addr)-8:] { + if self.bin[i] == 0 { + return false + } + } + + return true +} + +func (self *BloomFilter) Bin() []byte { + return self.bin +} diff --git a/ethchain/bloom_test.go b/ethchain/bloom_test.go new file mode 100644 index 000000000..ea53d539c --- /dev/null +++ b/ethchain/bloom_test.go @@ -0,0 +1,20 @@ +package ethchain + +import "testing" + +func TestBloomFilter(t *testing.T) { + bf := NewBloomFilter(nil) + + a := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} + bf.Set(a) + + b := []byte{10, 11, 12, 13, 14, 15, 16, 17, 18, 19} + + if bf.Search(a) == false { + t.Error("Expected 'a' to yield true using a bloom filter") + } + + if bf.Search(b) { + t.Error("Expected 'b' not to field trie using a bloom filter") + } +} diff --git a/ethchain/chain_manager.go b/ethchain/chain_manager.go new file mode 100644 index 000000000..d949f0ce7 --- /dev/null +++ b/ethchain/chain_manager.go @@ -0,0 +1,291 @@ +package ethchain + +import ( + "bytes" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/ethlog" + "github.com/ethereum/go-ethereum/ethutil" +) + +var chainlogger = ethlog.NewLogger("CHAIN") + +type ChainManager struct { + Ethereum EthManager + // The famous, the fabulous Mister GENESIIIIIIS (block) + genesisBlock *Block + // Last known total difficulty + TD *big.Int + + LastBlockNumber uint64 + + CurrentBlock *Block + LastBlockHash []byte +} + +func NewChainManager(ethereum EthManager) *ChainManager { + bc := &ChainManager{} + bc.genesisBlock = NewBlockFromBytes(ethutil.Encode(Genesis)) + bc.Ethereum = ethereum + + bc.setLastBlock() + + return bc +} + +func (bc *ChainManager) Genesis() *Block { + return bc.genesisBlock +} + +func (bc *ChainManager) NewBlock(coinbase []byte) *Block { + var root interface{} + hash := ZeroHash256 + + if bc.CurrentBlock != nil { + root = bc.CurrentBlock.state.Trie.Root + hash = bc.LastBlockHash + } + + block := CreateBlock( + root, + hash, + coinbase, + ethutil.BigPow(2, 32), + nil, + "") + + block.MinGasPrice = big.NewInt(10000000000000) + + parent := bc.CurrentBlock + if parent != nil { + block.Difficulty = CalcDifficulty(block, parent) + block.Number = new(big.Int).Add(bc.CurrentBlock.Number, ethutil.Big1) + block.GasLimit = block.CalcGasLimit(bc.CurrentBlock) + + } + + return block +} + +func CalcDifficulty(block, parent *Block) *big.Int { + diff := new(big.Int) + + adjust := new(big.Int).Rsh(parent.Difficulty, 10) + if block.Time >= parent.Time+5 { + diff.Sub(parent.Difficulty, adjust) + } else { + diff.Add(parent.Difficulty, adjust) + } + + return diff +} + +func (bc *ChainManager) Reset() { + AddTestNetFunds(bc.genesisBlock) + + bc.genesisBlock.state.Trie.Sync() + // Prepare the genesis block + bc.Add(bc.genesisBlock) + fk := append([]byte("bloom"), bc.genesisBlock.Hash()...) + bc.Ethereum.Db().Put(fk, make([]byte, 255)) + bc.CurrentBlock = bc.genesisBlock + + bc.SetTotalDifficulty(ethutil.Big("0")) + + // Set the last know difficulty (might be 0x0 as initial value, Genesis) + bc.TD = ethutil.BigD(ethutil.Config.Db.LastKnownTD()) +} + +func (bc *ChainManager) HasBlock(hash []byte) bool { + data, _ := ethutil.Config.Db.Get(hash) + return len(data) != 0 +} + +// TODO: At one point we might want to save a block by prevHash in the db to optimise this... +func (bc *ChainManager) HasBlockWithPrevHash(hash []byte) bool { + block := bc.CurrentBlock + + for ; block != nil; block = bc.GetBlock(block.PrevHash) { + if bytes.Compare(hash, block.PrevHash) == 0 { + return true + } + } + return false +} + +func (bc *ChainManager) CalculateBlockTD(block *Block) *big.Int { + blockDiff := new(big.Int) + + for _, uncle := range block.Uncles { + blockDiff = blockDiff.Add(blockDiff, uncle.Difficulty) + } + blockDiff = blockDiff.Add(blockDiff, block.Difficulty) + + return blockDiff +} + +func (bc *ChainManager) GenesisBlock() *Block { + return bc.genesisBlock +} + +func (self *ChainManager) GetChainHashesFromHash(hash []byte, max uint64) (chain [][]byte) { + block := self.GetBlock(hash) + if block == nil { + return + } + + // XXX Could be optimised by using a different database which only holds hashes (i.e., linked list) + for i := uint64(0); i < max; i++ { + chain = append(chain, block.Hash()) + + if block.Number.Cmp(ethutil.Big0) <= 0 { + break + } + + block = self.GetBlock(block.PrevHash) + } + + return +} + +func AddTestNetFunds(block *Block) { + for _, addr := range []string{ + "51ba59315b3a95761d0863b05ccc7a7f54703d99", + "e4157b34ea9615cfbde6b4fda419828124b70c78", + "b9c015918bdaba24b4ff057a92a3873d6eb201be", + "6c386a4b26f73c802f34673f7248bb118f97424a", + "cd2a3d9f938e13cd947ec05abc7fe734df8dd826", + "2ef47100e0787b915105fd5e3f4ff6752079d5cb", + "e6716f9544a56c530d868e4bfbacb172315bdead", + "1a26338f0d905e295fccb71fa9ea849ffa12aaf4", + } { + codedAddr := ethutil.Hex2Bytes(addr) + account := block.state.GetAccount(codedAddr) + account.SetBalance(ethutil.Big("1606938044258990275541962092341162602522202993782792835301376")) //ethutil.BigPow(2, 200) + block.state.UpdateStateObject(account) + } +} + +func (bc *ChainManager) setLastBlock() { + data, _ := ethutil.Config.Db.Get([]byte("LastBlock")) + if len(data) != 0 { + // Prep genesis + AddTestNetFunds(bc.genesisBlock) + + block := NewBlockFromBytes(data) + bc.CurrentBlock = block + bc.LastBlockHash = block.Hash() + bc.LastBlockNumber = block.Number.Uint64() + + // Set the last know difficulty (might be 0x0 as initial value, Genesis) + bc.TD = ethutil.BigD(ethutil.Config.Db.LastKnownTD()) + } else { + bc.Reset() + } + + chainlogger.Infof("Last block (#%d) %x\n", bc.LastBlockNumber, bc.CurrentBlock.Hash()) +} + +func (bc *ChainManager) SetTotalDifficulty(td *big.Int) { + ethutil.Config.Db.Put([]byte("LTD"), td.Bytes()) + bc.TD = td +} + +// Add a block to the chain and record addition information +func (bc *ChainManager) Add(block *Block) { + bc.writeBlockInfo(block) + // Prepare the genesis block + + bc.CurrentBlock = block + bc.LastBlockHash = block.Hash() + + encodedBlock := block.RlpEncode() + ethutil.Config.Db.Put(block.Hash(), encodedBlock) + ethutil.Config.Db.Put([]byte("LastBlock"), encodedBlock) +} + +func (self *ChainManager) CalcTotalDiff(block *Block) (*big.Int, error) { + parent := self.GetBlock(block.PrevHash) + if parent == nil { + return nil, fmt.Errorf("Unable to calculate total diff without known parent %x", block.PrevHash) + } + + parentTd := parent.BlockInfo().TD + + uncleDiff := new(big.Int) + for _, uncle := range block.Uncles { + uncleDiff = uncleDiff.Add(uncleDiff, uncle.Difficulty) + } + + td := new(big.Int) + td = td.Add(parentTd, uncleDiff) + td = td.Add(td, block.Difficulty) + + return td, nil +} + +func (bc *ChainManager) GetBlock(hash []byte) *Block { + data, _ := ethutil.Config.Db.Get(hash) + if len(data) == 0 { + return nil + } + + return NewBlockFromBytes(data) +} + +func (self *ChainManager) GetBlockByNumber(num uint64) *Block { + block := self.CurrentBlock + for ; block != nil; block = self.GetBlock(block.PrevHash) { + if block.Number.Uint64() == num { + break + } + } + + if block != nil && block.Number.Uint64() == 0 && num != 0 { + return nil + } + + return block +} + +func (self *ChainManager) GetBlockBack(num uint64) *Block { + block := self.CurrentBlock + + for ; num != 0 && block != nil; num-- { + block = self.GetBlock(block.PrevHash) + } + + return block +} + +func (bc *ChainManager) BlockInfoByHash(hash []byte) BlockInfo { + bi := BlockInfo{} + data, _ := ethutil.Config.Db.Get(append(hash, []byte("Info")...)) + bi.RlpDecode(data) + + return bi +} + +func (bc *ChainManager) BlockInfo(block *Block) BlockInfo { + bi := BlockInfo{} + data, _ := ethutil.Config.Db.Get(append(block.Hash(), []byte("Info")...)) + bi.RlpDecode(data) + + return bi +} + +// Unexported method for writing extra non-essential block info to the db +func (bc *ChainManager) writeBlockInfo(block *Block) { + bc.LastBlockNumber++ + bi := BlockInfo{Number: bc.LastBlockNumber, Hash: block.Hash(), Parent: block.PrevHash, TD: bc.TD} + + // For now we use the block hash with the words "info" appended as key + ethutil.Config.Db.Put(append(block.Hash(), []byte("Info")...), bi.RlpEncode()) +} + +func (bc *ChainManager) Stop() { + if bc.CurrentBlock != nil { + chainlogger.Infoln("Stopped") + } +} diff --git a/ethchain/chain_manager_test.go b/ethchain/chain_manager_test.go new file mode 100644 index 000000000..3603fd8a7 --- /dev/null +++ b/ethchain/chain_manager_test.go @@ -0,0 +1 @@ +package ethchain diff --git a/ethchain/dagger.go b/ethchain/dagger.go new file mode 100644 index 000000000..7efcf469d --- /dev/null +++ b/ethchain/dagger.go @@ -0,0 +1,239 @@ +package ethchain + +import ( + "hash" + "math/big" + "math/rand" + "time" + + "github.com/ethereum/go-ethereum/ethcrypto" + "github.com/ethereum/go-ethereum/ethlog" + "github.com/ethereum/go-ethereum/ethutil" + "github.com/obscuren/sha3" +) + +var powlogger = ethlog.NewLogger("POW") + +type PoW interface { + Search(block *Block, stop <-chan struct{}) []byte + Verify(hash []byte, diff *big.Int, nonce []byte) bool + GetHashrate() int64 + Turbo(bool) +} + +type EasyPow struct { + hash *big.Int + HashRate int64 + turbo bool +} + +func (pow *EasyPow) GetHashrate() int64 { + return pow.HashRate +} + +func (pow *EasyPow) Turbo(on bool) { + pow.turbo = on +} + +func (pow *EasyPow) Search(block *Block, stop <-chan struct{}) []byte { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + hash := block.HashNoNonce() + diff := block.Difficulty + i := int64(0) + start := time.Now().UnixNano() + t := time.Now() + + for { + select { + case <-stop: + powlogger.Infoln("Breaking from mining") + return nil + default: + i++ + + if time.Since(t) > (1 * time.Second) { + elapsed := time.Now().UnixNano() - start + hashes := ((float64(1e9) / float64(elapsed)) * float64(i)) / 1000 + pow.HashRate = int64(hashes) + powlogger.Infoln("Hashing @", int64(pow.HashRate), "khash") + + t = time.Now() + } + + sha := ethcrypto.Sha3(big.NewInt(r.Int63()).Bytes()) + if pow.Verify(hash, diff, sha) { + return sha + } + } + + if !pow.turbo { + time.Sleep(20 * time.Microsecond) + } + } + + return nil +} + +func (pow *EasyPow) Verify(hash []byte, diff *big.Int, nonce []byte) bool { + sha := sha3.NewKeccak256() + + d := append(hash, nonce...) + sha.Write(d) + + v := ethutil.BigPow(2, 256) + ret := new(big.Int).Div(v, diff) + + res := new(big.Int) + res.SetBytes(sha.Sum(nil)) + + return res.Cmp(ret) == -1 +} + +func (pow *EasyPow) SetHash(hash *big.Int) { +} + +type Dagger struct { + hash *big.Int + xn *big.Int +} + +var Found bool + +func (dag *Dagger) Find(obj *big.Int, resChan chan int64) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + + for i := 0; i < 1000; i++ { + rnd := r.Int63() + + res := dag.Eval(big.NewInt(rnd)) + powlogger.Infof("rnd %v\nres %v\nobj %v\n", rnd, res, obj) + if res.Cmp(obj) < 0 { + // Post back result on the channel + resChan <- rnd + // Notify other threads we've found a valid nonce + Found = true + } + + // Break out if found + if Found { + break + } + } + + resChan <- 0 +} + +func (dag *Dagger) Search(hash, diff *big.Int) *big.Int { + // TODO fix multi threading. Somehow it results in the wrong nonce + amountOfRoutines := 1 + + dag.hash = hash + + obj := ethutil.BigPow(2, 256) + obj = obj.Div(obj, diff) + + Found = false + resChan := make(chan int64, 3) + var res int64 + + for k := 0; k < amountOfRoutines; k++ { + go dag.Find(obj, resChan) + + // Wait for each go routine to finish + } + for k := 0; k < amountOfRoutines; k++ { + // Get the result from the channel. 0 = quit + if r := <-resChan; r != 0 { + res = r + } + } + + return big.NewInt(res) +} + +func (dag *Dagger) Verify(hash, diff, nonce *big.Int) bool { + dag.hash = hash + + obj := ethutil.BigPow(2, 256) + obj = obj.Div(obj, diff) + + return dag.Eval(nonce).Cmp(obj) < 0 +} + +func DaggerVerify(hash, diff, nonce *big.Int) bool { + dagger := &Dagger{} + dagger.hash = hash + + obj := ethutil.BigPow(2, 256) + obj = obj.Div(obj, diff) + + return dagger.Eval(nonce).Cmp(obj) < 0 +} + +func (dag *Dagger) Node(L uint64, i uint64) *big.Int { + if L == i { + return dag.hash + } + + var m *big.Int + if L == 9 { + m = big.NewInt(16) + } else { + m = big.NewInt(3) + } + + sha := sha3.NewKeccak256() + sha.Reset() + d := sha3.NewKeccak256() + b := new(big.Int) + ret := new(big.Int) + + for k := 0; k < int(m.Uint64()); k++ { + d.Reset() + d.Write(dag.hash.Bytes()) + d.Write(dag.xn.Bytes()) + d.Write(big.NewInt(int64(L)).Bytes()) + d.Write(big.NewInt(int64(i)).Bytes()) + d.Write(big.NewInt(int64(k)).Bytes()) + + b.SetBytes(Sum(d)) + pk := b.Uint64() & ((1 << ((L - 1) * 3)) - 1) + sha.Write(dag.Node(L-1, pk).Bytes()) + } + + ret.SetBytes(Sum(sha)) + + return ret +} + +func Sum(sha hash.Hash) []byte { + //in := make([]byte, 32) + return sha.Sum(nil) +} + +func (dag *Dagger) Eval(N *big.Int) *big.Int { + pow := ethutil.BigPow(2, 26) + dag.xn = pow.Div(N, pow) + + sha := sha3.NewKeccak256() + sha.Reset() + ret := new(big.Int) + + for k := 0; k < 4; k++ { + d := sha3.NewKeccak256() + b := new(big.Int) + + d.Reset() + d.Write(dag.hash.Bytes()) + d.Write(dag.xn.Bytes()) + d.Write(N.Bytes()) + d.Write(big.NewInt(int64(k)).Bytes()) + + b.SetBytes(Sum(d)) + pk := (b.Uint64() & 0x1ffffff) + + sha.Write(dag.Node(9, pk).Bytes()) + } + + return ret.SetBytes(Sum(sha)) +} diff --git a/ethchain/dagger_test.go b/ethchain/dagger_test.go new file mode 100644 index 000000000..2ffba0485 --- /dev/null +++ b/ethchain/dagger_test.go @@ -0,0 +1,19 @@ +package ethchain + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/ethutil" +) + +func BenchmarkDaggerSearch(b *testing.B) { + hash := big.NewInt(0) + diff := ethutil.BigPow(2, 36) + o := big.NewInt(0) // nonce doesn't matter. We're only testing against speed, not validity + + // Reset timer so the big generation isn't included in the benchmark + b.ResetTimer() + // Validate + DaggerVerify(hash, diff, o) +} diff --git a/ethchain/error.go b/ethchain/error.go new file mode 100644 index 000000000..82949141a --- /dev/null +++ b/ethchain/error.go @@ -0,0 +1,116 @@ +package ethchain + +import ( + "fmt" + "math/big" +) + +// Parent error. In case a parent is unknown this error will be thrown +// by the block manager +type ParentErr struct { + Message string +} + +func (err *ParentErr) Error() string { + return err.Message +} + +func ParentError(hash []byte) error { + return &ParentErr{Message: fmt.Sprintf("Block's parent unkown %x", hash)} +} + +func IsParentErr(err error) bool { + _, ok := err.(*ParentErr) + + return ok +} + +type UncleErr struct { + Message string +} + +func (err *UncleErr) Error() string { + return err.Message +} + +func UncleError(str string) error { + return &UncleErr{Message: str} +} + +func IsUncleErr(err error) bool { + _, ok := err.(*UncleErr) + + return ok +} + +// Block validation error. If any validation fails, this error will be thrown +type ValidationErr struct { + Message string +} + +func (err *ValidationErr) Error() string { + return err.Message +} + +func ValidationError(format string, v ...interface{}) *ValidationErr { + return &ValidationErr{Message: fmt.Sprintf(format, v...)} +} + +func IsValidationErr(err error) bool { + _, ok := err.(*ValidationErr) + + return ok +} + +type GasLimitErr struct { + Message string + Is, Max *big.Int +} + +func IsGasLimitErr(err error) bool { + _, ok := err.(*GasLimitErr) + + return ok +} +func (err *GasLimitErr) Error() string { + return err.Message +} +func GasLimitError(is, max *big.Int) *GasLimitErr { + return &GasLimitErr{Message: fmt.Sprintf("GasLimit error. Max %s, transaction would take it to %s", max, is), Is: is, Max: max} +} + +type NonceErr struct { + Message string + Is, Exp uint64 +} + +func (err *NonceErr) Error() string { + return err.Message +} + +func NonceError(is, exp uint64) *NonceErr { + return &NonceErr{Message: fmt.Sprintf("Nonce err. Is %d, expected %d", is, exp), Is: is, Exp: exp} +} + +func IsNonceErr(err error) bool { + _, ok := err.(*NonceErr) + + return ok +} + +type OutOfGasErr struct { + Message string +} + +func OutOfGasError() *OutOfGasErr { + return &OutOfGasErr{Message: "Out of gas"} +} +func (self *OutOfGasErr) Error() string { + return self.Message +} + +func IsOutOfGasErr(err error) bool { + _, ok := err.(*OutOfGasErr) + + return ok +} diff --git a/ethchain/events.go b/ethchain/events.go new file mode 100644 index 000000000..05c21edfe --- /dev/null +++ b/ethchain/events.go @@ -0,0 +1,10 @@ +package ethchain + +type TxEvent struct { + Type int // TxPre || TxPost + Tx *Transaction +} + +type NewBlockEvent struct { + Block *Block +} diff --git a/ethchain/fees.go b/ethchain/fees.go new file mode 100644 index 000000000..901357439 --- /dev/null +++ b/ethchain/fees.go @@ -0,0 +1,7 @@ +package ethchain + +import ( + "math/big" +) + +var BlockReward *big.Int = big.NewInt(1.5e+18) diff --git a/ethchain/filter.go b/ethchain/filter.go new file mode 100644 index 000000000..4e7fe68a8 --- /dev/null +++ b/ethchain/filter.go @@ -0,0 +1,206 @@ +package ethchain + +import ( + "bytes" + "fmt" + "math" + + "github.com/ethereum/go-ethereum/ethstate" +) + +type AccountChange struct { + Address, StateAddress []byte +} + +// Filtering interface +type Filter struct { + eth EthManager + earliest int64 + latest int64 + skip int + from, to [][]byte + max int + + Altered []AccountChange + + BlockCallback func(*Block) + MessageCallback func(ethstate.Messages) +} + +// Create a new filter which uses a bloom filter on blocks to figure out whether a particular block +// is interesting or not. +func NewFilter(eth EthManager) *Filter { + return &Filter{eth: eth} +} + +func (self *Filter) AddAltered(address, stateAddress []byte) { + self.Altered = append(self.Altered, AccountChange{address, stateAddress}) +} + +// Set the earliest and latest block for filtering. +// -1 = latest block (i.e., the current block) +// hash = particular hash from-to +func (self *Filter) SetEarliestBlock(earliest int64) { + self.earliest = earliest +} + +func (self *Filter) SetLatestBlock(latest int64) { + self.latest = latest +} + +func (self *Filter) SetFrom(addr [][]byte) { + self.from = addr +} + +func (self *Filter) AddFrom(addr []byte) { + self.from = append(self.from, addr) +} + +func (self *Filter) SetTo(addr [][]byte) { + self.to = addr +} + +func (self *Filter) AddTo(addr []byte) { + self.to = append(self.to, addr) +} + +func (self *Filter) SetMax(max int) { + self.max = max +} + +func (self *Filter) SetSkip(skip int) { + self.skip = skip +} + +// Run filters messages with the current parameters set +func (self *Filter) Find() []*ethstate.Message { + var earliestBlockNo uint64 = uint64(self.earliest) + if self.earliest == -1 { + earliestBlockNo = self.eth.ChainManager().CurrentBlock.Number.Uint64() + } + var latestBlockNo uint64 = uint64(self.latest) + if self.latest == -1 { + latestBlockNo = self.eth.ChainManager().CurrentBlock.Number.Uint64() + } + + var ( + messages []*ethstate.Message + block = self.eth.ChainManager().GetBlockByNumber(latestBlockNo) + quit bool + ) + for i := 0; !quit && block != nil; i++ { + // Quit on latest + switch { + case block.Number.Uint64() == earliestBlockNo: + quit = true + case self.max <= len(messages): + break + } + + // Use bloom filtering to see if this block is interesting given the + // current parameters + if self.bloomFilter(block) { + // Get the messages of the block + msgs, err := self.eth.StateManager().GetMessages(block) + if err != nil { + chainlogger.Warnln("err: filter get messages ", err) + + break + } + + messages = append(messages, self.FilterMessages(msgs)...) + } + + block = self.eth.ChainManager().GetBlock(block.PrevHash) + } + + skip := int(math.Min(float64(len(messages)), float64(self.skip))) + + return messages[skip:] +} + +func includes(addresses [][]byte, a []byte) (found bool) { + for _, addr := range addresses { + if bytes.Compare(addr, a) == 0 { + return true + } + } + + return +} + +func (self *Filter) FilterMessages(msgs []*ethstate.Message) []*ethstate.Message { + var messages []*ethstate.Message + + // Filter the messages for interesting stuff + for _, message := range msgs { + if len(self.to) > 0 && !includes(self.to, message.To) { + continue + } + + if len(self.from) > 0 && !includes(self.from, message.From) { + continue + } + + var match bool + if len(self.Altered) == 0 { + match = true + } + + for _, accountChange := range self.Altered { + if len(accountChange.Address) > 0 && bytes.Compare(message.To, accountChange.Address) != 0 { + continue + } + + if len(accountChange.StateAddress) > 0 && !includes(message.ChangedAddresses, accountChange.StateAddress) { + continue + } + + match = true + break + } + + if !match { + continue + } + + messages = append(messages, message) + } + + return messages +} + +func (self *Filter) bloomFilter(block *Block) bool { + fk := append([]byte("bloom"), block.Hash()...) + bin, err := self.eth.Db().Get(fk) + if err != nil { + fmt.Println(err) + } + + bloom := NewBloomFilter(bin) + + var fromIncluded, toIncluded bool + if len(self.from) > 0 { + for _, from := range self.from { + if bloom.Search(from) { + fromIncluded = true + break + } + } + } else { + fromIncluded = true + } + + if len(self.to) > 0 { + for _, to := range self.to { + if bloom.Search(to) { + toIncluded = true + break + } + } + } else { + toIncluded = true + } + + return fromIncluded && toIncluded +} diff --git a/ethchain/filter_test.go b/ethchain/filter_test.go new file mode 100644 index 000000000..e569b3774 --- /dev/null +++ b/ethchain/filter_test.go @@ -0,0 +1,7 @@ +package ethchain + +import "testing" + +func TestFilter(t *testing.T) { + NewFilter(NewTestManager()) +} diff --git a/ethchain/genesis.go b/ethchain/genesis.go new file mode 100644 index 000000000..232986d53 --- /dev/null +++ b/ethchain/genesis.go @@ -0,0 +1,48 @@ +package ethchain + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/ethcrypto" + "github.com/ethereum/go-ethereum/ethutil" +) + +/* + * This is the special genesis block. + */ + +var ZeroHash256 = make([]byte, 32) +var ZeroHash160 = make([]byte, 20) +var EmptyShaList = ethcrypto.Sha3(ethutil.Encode([]interface{}{})) + +var GenesisHeader = []interface{}{ + // Previous hash (none) + ZeroHash256, + // Empty uncles + "", + // Coinbase + ZeroHash160, + // Root state + "", + // tx sha + "", + // Difficulty + //ethutil.BigPow(2, 22), + big.NewInt(131072), + // Number + ethutil.Big0, + // Block minimum gas price + ethutil.Big0, + // Block upper gas bound + big.NewInt(1000000), + // Block gas used + ethutil.Big0, + // Time + ethutil.Big0, + // Extra + nil, + // Nonce + ethcrypto.Sha3(big.NewInt(42).Bytes()), +} + +var Genesis = []interface{}{GenesisHeader, []interface{}{}, []interface{}{}} diff --git a/ethchain/helper_test.go b/ethchain/helper_test.go new file mode 100644 index 000000000..e4aef67d0 --- /dev/null +++ b/ethchain/helper_test.go @@ -0,0 +1,92 @@ +package ethchain + +import ( + "container/list" + "fmt" + + "github.com/ethereum/go-ethereum/ethcrypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethutil" + "github.com/ethereum/go-ethereum/ethwire" + "github.com/ethereum/go-ethereum/event" +) + +// Implement our EthTest Manager +type TestManager struct { + stateManager *StateManager + eventMux *event.TypeMux + + db ethutil.Database + txPool *TxPool + blockChain *BlockChain + Blocks []*Block +} + +func (s *TestManager) IsListening() bool { + return false +} + +func (s *TestManager) IsMining() bool { + return false +} + +func (s *TestManager) PeerCount() int { + return 0 +} + +func (s *TestManager) Peers() *list.List { + return list.New() +} + +func (s *TestManager) BlockChain() *BlockChain { + return s.blockChain +} + +func (tm *TestManager) TxPool() *TxPool { + return tm.txPool +} + +func (tm *TestManager) StateManager() *StateManager { + return tm.stateManager +} + +func (tm *TestManager) EventMux() *event.TypeMux { + return tm.eventMux +} +func (tm *TestManager) Broadcast(msgType ethwire.MsgType, data []interface{}) { + fmt.Println("Broadcast not implemented") +} + +func (tm *TestManager) ClientIdentity() ethwire.ClientIdentity { + return nil +} +func (tm *TestManager) KeyManager() *ethcrypto.KeyManager { + return nil +} + +func (tm *TestManager) Db() ethutil.Database { + return tm.db +} + +func NewTestManager() *TestManager { + ethutil.ReadConfig(".ethtest", "/tmp/ethtest", "ETH") + + db, err := ethdb.NewMemDatabase() + if err != nil { + fmt.Println("Could not create mem-db, failing") + return nil + } + ethutil.Config.Db = db + + testManager := &TestManager{} + testManager.eventMux = new(event.TypeMux) + testManager.db = db + testManager.txPool = NewTxPool(testManager) + testManager.blockChain = NewBlockChain(testManager) + testManager.stateManager = NewStateManager(testManager) + + // Start the tx pool + testManager.txPool.Start() + + return testManager +} diff --git a/ethchain/state_manager.go b/ethchain/state_manager.go new file mode 100644 index 000000000..1de46b371 --- /dev/null +++ b/ethchain/state_manager.go @@ -0,0 +1,446 @@ +package ethchain + +import ( + "bytes" + "container/list" + "fmt" + "math/big" + "os" + "sync" + "time" + + "github.com/ethereum/go-ethereum/ethcrypto" + "github.com/ethereum/go-ethereum/ethlog" + "github.com/ethereum/go-ethereum/ethstate" + "github.com/ethereum/go-ethereum/ethutil" + "github.com/ethereum/go-ethereum/ethwire" + "github.com/ethereum/go-ethereum/event" +) + +var statelogger = ethlog.NewLogger("STATE") + +type Peer interface { + Inbound() bool + LastSend() time.Time + LastPong() int64 + Host() []byte + Port() uint16 + Version() string + PingTime() string + Connected() *int32 + Caps() *ethutil.Value +} + +type EthManager interface { + StateManager() *StateManager + ChainManager() *ChainManager + TxPool() *TxPool + Broadcast(msgType ethwire.MsgType, data []interface{}) + PeerCount() int + IsMining() bool + IsListening() bool + Peers() *list.List + KeyManager() *ethcrypto.KeyManager + ClientIdentity() ethwire.ClientIdentity + Db() ethutil.Database + EventMux() *event.TypeMux +} + +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 *ChainManager + // non-persistent key/value memory storage + mem map[string]*big.Int + // Proof of work used for validating + Pow PoW + // The ethereum manager interface + eth EthManager + // The managed states + // Transiently state. The trans state isn't ever saved, validated and + // it could be used for setting account nonces without effecting + // the main states. + transState *ethstate.State + // Mining state. The mining state is used purely and solely by the mining + // operation. + miningState *ethstate.State + + // The last attempted block is mainly used for debugging purposes + // This does not have to be a valid block and will be set during + // 'Process' & canonical validation. + lastAttemptedBlock *Block + + events event.Subscription +} + +func NewStateManager(ethereum EthManager) *StateManager { + sm := &StateManager{ + mem: make(map[string]*big.Int), + Pow: &EasyPow{}, + eth: ethereum, + bc: ethereum.ChainManager(), + } + sm.transState = ethereum.ChainManager().CurrentBlock.State().Copy() + sm.miningState = ethereum.ChainManager().CurrentBlock.State().Copy() + + return sm +} + +func (self *StateManager) Start() { + statelogger.Debugln("Starting state manager") + self.events = self.eth.EventMux().Subscribe(Blocks(nil)) + go self.updateThread() +} + +func (self *StateManager) Stop() { + statelogger.Debugln("Stopping state manager") + self.events.Unsubscribe() +} + +func (self *StateManager) updateThread() { + for ev := range self.events.Chan() { + for _, block := range ev.(Blocks) { + err := self.Process(block, false) + if err != nil { + statelogger.Infoln(err) + statelogger.Debugf("Block #%v failed (%x...)\n", block.Number, block.Hash()[0:4]) + statelogger.Debugln(block) + break + } + } + } +} + +func (sm *StateManager) CurrentState() *ethstate.State { + return sm.eth.ChainManager().CurrentBlock.State() +} + +func (sm *StateManager) TransState() *ethstate.State { + return sm.transState +} + +func (sm *StateManager) MiningState() *ethstate.State { + return sm.miningState +} + +func (sm *StateManager) NewMiningState() *ethstate.State { + sm.miningState = sm.eth.ChainManager().CurrentBlock.State().Copy() + + return sm.miningState +} + +func (sm *StateManager) ChainManager() *ChainManager { + return sm.bc +} + +func (self *StateManager) ProcessTransactions(coinbase *ethstate.StateObject, state *ethstate.State, block, parent *Block, txs Transactions) (Receipts, Transactions, Transactions, error) { + var ( + receipts Receipts + handled, unhandled Transactions + totalUsedGas = big.NewInt(0) + err error + ) + +done: + for i, tx := range txs { + txGas := new(big.Int).Set(tx.Gas) + + cb := state.GetStateObject(coinbase.Address()) + st := NewStateTransition(cb, tx, state, block) + err = st.TransitionState() + if err != nil { + statelogger.Infoln(err) + switch { + case IsNonceErr(err): + err = nil // ignore error + continue + case IsGasLimitErr(err): + unhandled = txs[i:] + + break done + default: + statelogger.Infoln(err) + err = nil + //return nil, nil, nil, err + } + } + + // Update the state with pending changes + state.Update() + + txGas.Sub(txGas, st.gas) + accumelative := new(big.Int).Set(totalUsedGas.Add(totalUsedGas, txGas)) + receipt := &Receipt{tx, ethutil.CopyBytes(state.Root().([]byte)), accumelative} + + if i < len(block.Receipts()) { + original := block.Receipts()[i] + if !original.Cmp(receipt) { + if ethutil.Config.Diff { + os.Exit(1) + } + + err := fmt.Errorf("#%d receipt failed (r) %v ~ %x <=> (c) %v ~ %x (%x...)", i+1, original.CumulativeGasUsed, original.PostState[0:4], receipt.CumulativeGasUsed, receipt.PostState[0:4], receipt.Tx.Hash()[0:4]) + + return nil, nil, nil, err + } + } + + // Notify all subscribers + self.eth.EventMux().Post(TxEvent{TxPost, tx}) + + receipts = append(receipts, receipt) + handled = append(handled, tx) + + if ethutil.Config.Diff && ethutil.Config.DiffType == "all" { + state.CreateOutputForDiff() + } + } + + parent.GasUsed = totalUsedGas + + return receipts, handled, unhandled, err +} + +func (sm *StateManager) Process(block *Block, dontReact bool) (err error) { + // Processing a blocks may never happen simultaneously + sm.mutex.Lock() + defer sm.mutex.Unlock() + + if sm.bc.HasBlock(block.Hash()) { + return nil + } + + if !sm.bc.HasBlock(block.PrevHash) { + return ParentError(block.PrevHash) + } + + sm.lastAttemptedBlock = block + + var ( + parent = sm.bc.GetBlock(block.PrevHash) + state = parent.State() + ) + + // 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 state.Reset() + + if ethutil.Config.Diff && ethutil.Config.DiffType == "all" { + fmt.Printf("## %x %x ##\n", block.Hash(), block.Number) + } + + receipts, err := sm.ApplyDiff(state, parent, block) + if err != nil { + return err + } + + txSha := CreateTxSha(receipts) + if bytes.Compare(txSha, block.TxSha) != 0 { + return fmt.Errorf("Error validating tx sha. Received %x, got %x", block.TxSha, txSha) + } + + // Block validation + if err = sm.ValidateBlock(block); err != nil { + statelogger.Errorln("Error validating block:", err) + return err + } + + if err = sm.AccumelateRewards(state, block, parent); err != nil { + statelogger.Errorln("Error accumulating reward", err) + return err + } + + state.Update() + + if !block.State().Cmp(state) { + err = fmt.Errorf("Invalid merkle root.\nrec: %x\nis: %x", block.State().Trie.Root, state.Trie.Root) + return + } + + // 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 + state.Sync() + + // Add the block to the chain + sm.bc.Add(block) + + sm.transState = state.Copy() + + // Create a bloom bin for this block + filter := sm.createBloomFilter(state) + // Persist the data + fk := append([]byte("bloom"), block.Hash()...) + sm.eth.Db().Put(fk, filter.Bin()) + + statelogger.Infof("Imported block #%d (%x...)\n", block.Number, block.Hash()[0:4]) + if dontReact == false { + sm.eth.EventMux().Post(NewBlockEvent{block}) + + state.Manifest().Reset() + } + + sm.eth.TxPool().RemoveInvalid(state) + } else { + statelogger.Errorln("total diff failed") + } + + return nil +} + +func (sm *StateManager) ApplyDiff(state *ethstate.State, parent, block *Block) (receipts Receipts, err error) { + coinbase := state.GetOrNewStateObject(block.Coinbase) + coinbase.SetGasPool(block.CalcGasLimit(parent)) + + // Process the transactions on to current block + receipts, _, _, err = sm.ProcessTransactions(coinbase, state, block, parent, block.Transactions()) + if err != nil { + return nil, err + } + + return receipts, 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 { + // Check each uncle's previous hash. In order for it to be valid + // is if it has the same block hash as the current + parent := sm.bc.GetBlock(block.PrevHash) + /* + for _, uncle := range block.Uncles { + if bytes.Compare(uncle.PrevHash,parent.PrevHash) != 0 { + return ValidationError("Mismatch uncle's previous hash. Expected %x, got %x",parent.PrevHash, uncle.PrevHash) + } + } + */ + + expd := CalcDifficulty(block, parent) + if expd.Cmp(block.Difficulty) < 0 { + return fmt.Errorf("Difficulty check failed for block %v, %v", block.Difficulty, expd) + } + + diff := block.Time - parent.Time + if diff < 0 { + return ValidationError("Block timestamp less then prev block %v (%v - %v)", diff, block.Time, sm.bc.CurrentBlock.Time) + } + + /* XXX + // 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.Bytes2Hex(block.Nonce)) + } + + return nil +} + +func (sm *StateManager) AccumelateRewards(state *ethstate.State, block, parent *Block) error { + reward := new(big.Int).Set(BlockReward) + + knownUncles := ethutil.Set(parent.Uncles) + nonces := ethutil.NewSet(block.Nonce) + for _, uncle := range block.Uncles { + if nonces.Include(uncle.Nonce) { + // Error not unique + return UncleError("Uncle not unique") + } + + uncleParent := sm.bc.GetBlock(uncle.PrevHash) + if uncleParent == nil { + return UncleError("Uncle's parent unknown") + } + + if uncleParent.Number.Cmp(new(big.Int).Sub(parent.Number, big.NewInt(6))) < 0 { + return UncleError("Uncle too old") + } + + if knownUncles.Include(uncle.Hash()) { + return UncleError("Uncle in chain") + } + + nonces.Insert(uncle.Nonce) + + r := new(big.Int) + r.Mul(BlockReward, big.NewInt(15)).Div(r, big.NewInt(16)) + + uncleAccount := state.GetAccount(uncle.Coinbase) + uncleAccount.AddAmount(r) + + reward.Add(reward, new(big.Int).Div(BlockReward, big.NewInt(32))) + } + + // Get the account associated with the coinbase + account := state.GetAccount(block.Coinbase) + // Reward amount of ether to the coinbase address + account.AddAmount(reward) + + return nil +} + +// Manifest will handle both creating notifications and generating bloom bin data +func (sm *StateManager) createBloomFilter(state *ethstate.State) *BloomFilter { + bloomf := NewBloomFilter(nil) + + for _, msg := range state.Manifest().Messages { + bloomf.Set(msg.To) + bloomf.Set(msg.From) + } + + sm.eth.EventMux().Post(state.Manifest().Messages) + + return bloomf +} + +func (sm *StateManager) GetMessages(block *Block) (messages []*ethstate.Message, err error) { + if !sm.bc.HasBlock(block.PrevHash) { + return nil, ParentError(block.PrevHash) + } + + sm.lastAttemptedBlock = block + + var ( + parent = sm.bc.GetBlock(block.PrevHash) + state = parent.State().Copy() + ) + + defer state.Reset() + + sm.ApplyDiff(state, parent, block) + + sm.AccumelateRewards(state, block, parent) + + return state.Manifest().Messages, nil +} diff --git a/ethchain/state_transition.go b/ethchain/state_transition.go new file mode 100644 index 000000000..1274915d5 --- /dev/null +++ b/ethchain/state_transition.go @@ -0,0 +1,276 @@ +package ethchain + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/ethstate" + "github.com/ethereum/go-ethereum/ethtrie" + "github.com/ethereum/go-ethereum/ethutil" + "github.com/ethereum/go-ethereum/vm" +) + +/* + * The State transitioning model + * + * A state transition is a change made when a transaction is applied to the current world state + * The state transitioning model does all all the necessary work to work out a valid new state root. + * 1) Nonce handling + * 2) Pre pay / buy gas of the coinbase (miner) + * 3) Create a new state object if the recipient is \0*32 + * 4) Value transfer + * == If contract creation == + * 4a) Attempt to run transaction data + * 4b) If valid, use result as code for the new state object + * == end == + * 5) Run Script section + * 6) Derive new state root + */ +type StateTransition struct { + coinbase, receiver []byte + tx *Transaction + gas, gasPrice *big.Int + value *big.Int + data []byte + state *ethstate.State + block *Block + + cb, rec, sen *ethstate.StateObject +} + +func NewStateTransition(coinbase *ethstate.StateObject, tx *Transaction, state *ethstate.State, block *Block) *StateTransition { + return &StateTransition{coinbase.Address(), tx.Recipient, tx, new(big.Int), new(big.Int).Set(tx.GasPrice), tx.Value, tx.Data, state, block, coinbase, nil, nil} +} + +func (self *StateTransition) Coinbase() *ethstate.StateObject { + if self.cb != nil { + return self.cb + } + + self.cb = self.state.GetOrNewStateObject(self.coinbase) + return self.cb +} +func (self *StateTransition) Sender() *ethstate.StateObject { + if self.sen != nil { + return self.sen + } + + self.sen = self.state.GetOrNewStateObject(self.tx.Sender()) + + return self.sen +} +func (self *StateTransition) Receiver() *ethstate.StateObject { + if self.tx != nil && self.tx.CreatesContract() { + return nil + } + + if self.rec != nil { + return self.rec + } + + self.rec = self.state.GetOrNewStateObject(self.tx.Recipient) + return self.rec +} + +func (self *StateTransition) UseGas(amount *big.Int) error { + if self.gas.Cmp(amount) < 0 { + return OutOfGasError() + } + self.gas.Sub(self.gas, amount) + + return nil +} + +func (self *StateTransition) AddGas(amount *big.Int) { + self.gas.Add(self.gas, amount) +} + +func (self *StateTransition) BuyGas() error { + var err error + + sender := self.Sender() + if sender.Balance().Cmp(self.tx.GasValue()) < 0 { + return fmt.Errorf("Insufficient funds to pre-pay gas. Req %v, has %v", self.tx.GasValue(), sender.Balance()) + } + + coinbase := self.Coinbase() + err = coinbase.BuyGas(self.tx.Gas, self.tx.GasPrice) + if err != nil { + return err + } + + self.AddGas(self.tx.Gas) + sender.SubAmount(self.tx.GasValue()) + + return nil +} + +func (self *StateTransition) RefundGas() { + coinbase, sender := self.Coinbase(), self.Sender() + coinbase.RefundGas(self.gas, self.tx.GasPrice) + + // Return remaining gas + remaining := new(big.Int).Mul(self.gas, self.tx.GasPrice) + sender.AddAmount(remaining) +} + +func (self *StateTransition) preCheck() (err error) { + var ( + tx = self.tx + sender = self.Sender() + ) + + // Make sure this transaction's nonce is correct + if sender.Nonce != tx.Nonce { + return NonceError(tx.Nonce, sender.Nonce) + } + + // Pre-pay gas / Buy gas of the coinbase account + if err = self.BuyGas(); err != nil { + return err + } + + return nil +} + +func (self *StateTransition) TransitionState() (err error) { + statelogger.Debugf("(~) %x\n", self.tx.Hash()) + + defer func() { + if r := recover(); r != nil { + statelogger.Infoln(r) + err = fmt.Errorf("state transition err %v", r) + } + }() + + // XXX Transactions after this point are considered valid. + if err = self.preCheck(); err != nil { + return + } + + var ( + tx = self.tx + sender = self.Sender() + receiver *ethstate.StateObject + ) + + defer self.RefundGas() + + // Increment the nonce for the next transaction + sender.Nonce += 1 + + // Transaction gas + if err = self.UseGas(vm.GasTx); err != nil { + return + } + + // Pay data gas + dataPrice := big.NewInt(int64(len(self.data))) + dataPrice.Mul(dataPrice, vm.GasData) + if err = self.UseGas(dataPrice); err != nil { + return + } + + if sender.Balance().Cmp(self.value) < 0 { + return fmt.Errorf("Insufficient funds to transfer value. Req %v, has %v", self.value, sender.Balance) + } + + var snapshot *ethstate.State + // If the receiver is nil it's a contract (\0*32). + if tx.CreatesContract() { + // Subtract the (irreversible) amount from the senders account + sender.SubAmount(self.value) + + snapshot = self.state.Copy() + + // Create a new state object for the contract + receiver := MakeContract(tx, self.state) + self.rec = receiver + if receiver == nil { + return fmt.Errorf("Unable to create contract") + } + + // Add the amount to receivers account which should conclude this transaction + receiver.AddAmount(self.value) + } else { + receiver = self.Receiver() + + // Subtract the amount from the senders account + sender.SubAmount(self.value) + // Add the amount to receivers account which should conclude this transaction + receiver.AddAmount(self.value) + + snapshot = self.state.Copy() + } + + msg := self.state.Manifest().AddMessage(ðstate.Message{ + To: receiver.Address(), From: sender.Address(), + Input: self.tx.Data, + Origin: sender.Address(), + Block: self.block.Hash(), Timestamp: self.block.Time, Coinbase: self.block.Coinbase, Number: self.block.Number, + Value: self.value, + }) + + // Process the init code and create 'valid' contract + if IsContractAddr(self.receiver) { + // Evaluate the initialization script + // and use the return value as the + // script section for the state object. + self.data = nil + + code, err := self.Eval(msg, receiver.Init(), receiver) + if err != nil { + self.state.Set(snapshot) + + return fmt.Errorf("Error during init execution %v", err) + } + + receiver.Code = code + msg.Output = code + } else { + if len(receiver.Code) > 0 { + ret, err := self.Eval(msg, receiver.Code, receiver) + if err != nil { + self.state.Set(snapshot) + + return fmt.Errorf("Error during code execution %v", err) + } + + msg.Output = ret + } + } + + return +} + +func (self *StateTransition) Eval(msg *ethstate.Message, script []byte, context *ethstate.StateObject) (ret []byte, err error) { + var ( + transactor = self.Sender() + state = self.state + env = NewEnv(state, self.tx, self.block) + callerClosure = vm.NewClosure(msg, transactor, context, script, self.gas, self.gasPrice) + ) + + //vm := vm.New(env, vm.Type(ethutil.Config.VmType)) + evm := vm.New(env, vm.DebugVmTy) + + ret, _, err = callerClosure.Call(evm, self.tx.Data) + + return +} + +// Converts an transaction in to a state object +func MakeContract(tx *Transaction, state *ethstate.State) *ethstate.StateObject { + // Create contract if there's no recipient + if tx.IsContract() { + addr := tx.CreationAddress(state) + + contract := state.GetOrNewStateObject(addr) + contract.InitCode = tx.Data + contract.State = ethstate.New(ethtrie.New(ethutil.Config.Db, "")) + + return contract + } + + return nil +} diff --git a/ethchain/transaction.go b/ethchain/transaction.go new file mode 100644 index 000000000..c78c27ed6 --- /dev/null +++ b/ethchain/transaction.go @@ -0,0 +1,257 @@ +package ethchain + +import ( + "bytes" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/ethcrypto" + "github.com/ethereum/go-ethereum/ethstate" + "github.com/ethereum/go-ethereum/ethutil" + "github.com/obscuren/secp256k1-go" +) + +var ContractAddr = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +func IsContractAddr(addr []byte) bool { + return len(addr) == 0 + //return bytes.Compare(addr, ContractAddr) == 0 +} + +type Transaction struct { + Nonce uint64 + Recipient []byte + Value *big.Int + Gas *big.Int + GasPrice *big.Int + Data []byte + v byte + r, s []byte + + // Indicates whether this tx is a contract creation transaction + contractCreation bool +} + +func NewContractCreationTx(value, gas, gasPrice *big.Int, script []byte) *Transaction { + return &Transaction{Recipient: nil, Value: value, Gas: gas, GasPrice: gasPrice, Data: script, contractCreation: true} +} + +func NewTransactionMessage(to []byte, value, gas, gasPrice *big.Int, data []byte) *Transaction { + return &Transaction{Recipient: to, Value: value, GasPrice: gasPrice, Gas: gas, Data: data} +} + +func NewTransactionFromBytes(data []byte) *Transaction { + tx := &Transaction{} + tx.RlpDecode(data) + + return tx +} + +func NewTransactionFromValue(val *ethutil.Value) *Transaction { + tx := &Transaction{} + tx.RlpValueDecode(val) + + return tx +} + +func (self *Transaction) GasValue() *big.Int { + return new(big.Int).Mul(self.Gas, self.GasPrice) +} + +func (self *Transaction) TotalValue() *big.Int { + v := self.GasValue() + return v.Add(v, self.Value) +} + +func (tx *Transaction) Hash() []byte { + data := []interface{}{tx.Nonce, tx.GasPrice, tx.Gas, tx.Recipient, tx.Value, tx.Data} + + return ethcrypto.Sha3(ethutil.NewValue(data).Encode()) +} + +func (tx *Transaction) CreatesContract() bool { + return tx.contractCreation +} + +/* Deprecated */ +func (tx *Transaction) IsContract() bool { + return tx.CreatesContract() +} + +func (tx *Transaction) CreationAddress(state *ethstate.State) []byte { + // Generate a new address + addr := ethcrypto.Sha3(ethutil.NewValue([]interface{}{tx.Sender(), tx.Nonce}).Encode())[12:] + //for i := uint64(0); state.GetStateObject(addr) != nil; i++ { + // addr = ethcrypto.Sha3(ethutil.NewValue([]interface{}{tx.Sender(), tx.Nonce + i}).Encode())[12:] + //} + + return addr +} + +func (tx *Transaction) Signature(key []byte) []byte { + hash := tx.Hash() + + sig, _ := secp256k1.Sign(hash, key) + + return sig +} + +func (tx *Transaction) PublicKey() []byte { + hash := tx.Hash() + + // TODO + r := ethutil.LeftPadBytes(tx.r, 32) + s := ethutil.LeftPadBytes(tx.s, 32) + + sig := append(r, s...) + sig = append(sig, tx.v-27) + + pubkey, _ := secp256k1.RecoverPubkey(hash, sig) + + return pubkey +} + +func (tx *Transaction) Sender() []byte { + pubkey := tx.PublicKey() + + // Validate the returned key. + // Return nil if public key isn't in full format + if pubkey[0] != 4 { + return nil + } + + return ethcrypto.Sha3(pubkey[1:])[12:] +} + +func (tx *Transaction) Sign(privk []byte) error { + + sig := tx.Signature(privk) + + tx.r = sig[:32] + tx.s = sig[32:64] + tx.v = sig[64] + 27 + + return nil +} + +func (tx *Transaction) RlpData() interface{} { + data := []interface{}{tx.Nonce, tx.GasPrice, tx.Gas, tx.Recipient, tx.Value, tx.Data} + + // TODO Remove prefixing zero's + + return append(data, tx.v, new(big.Int).SetBytes(tx.r).Bytes(), new(big.Int).SetBytes(tx.s).Bytes()) +} + +func (tx *Transaction) RlpValue() *ethutil.Value { + return ethutil.NewValue(tx.RlpData()) +} + +func (tx *Transaction) RlpEncode() []byte { + return tx.RlpValue().Encode() +} + +func (tx *Transaction) RlpDecode(data []byte) { + tx.RlpValueDecode(ethutil.NewValueFromBytes(data)) +} + +func (tx *Transaction) RlpValueDecode(decoder *ethutil.Value) { + tx.Nonce = decoder.Get(0).Uint() + tx.GasPrice = decoder.Get(1).BigInt() + tx.Gas = decoder.Get(2).BigInt() + tx.Recipient = decoder.Get(3).Bytes() + tx.Value = decoder.Get(4).BigInt() + tx.Data = decoder.Get(5).Bytes() + tx.v = byte(decoder.Get(6).Uint()) + + tx.r = decoder.Get(7).Bytes() + tx.s = decoder.Get(8).Bytes() + + if IsContractAddr(tx.Recipient) { + tx.contractCreation = true + } +} + +func (tx *Transaction) String() string { + return fmt.Sprintf(` + TX(%x) + Contract: %v + From: %x + To: %x + Nonce: %v + GasPrice: %v + Gas: %v + Value: %v + Data: 0x%x + V: 0x%x + R: 0x%x + S: 0x%x + `, + tx.Hash(), + len(tx.Recipient) == 0, + tx.Sender(), + tx.Recipient, + tx.Nonce, + tx.GasPrice, + tx.Gas, + tx.Value, + tx.Data, + tx.v, + tx.r, + tx.s) +} + +type Receipt struct { + Tx *Transaction + PostState []byte + CumulativeGasUsed *big.Int +} +type Receipts []*Receipt + +func NewRecieptFromValue(val *ethutil.Value) *Receipt { + r := &Receipt{} + r.RlpValueDecode(val) + + return r +} + +func (self *Receipt) RlpValueDecode(decoder *ethutil.Value) { + self.Tx = NewTransactionFromValue(decoder.Get(0)) + self.PostState = decoder.Get(1).Bytes() + self.CumulativeGasUsed = decoder.Get(2).BigInt() +} + +func (self *Receipt) RlpData() interface{} { + return []interface{}{self.Tx.RlpData(), self.PostState, self.CumulativeGasUsed} +} + +func (self *Receipt) String() string { + return fmt.Sprintf(` + R + Tx:[ %v] + PostState: 0x%x + CumulativeGasUsed: %v + `, + self.Tx, + self.PostState, + self.CumulativeGasUsed) +} + +func (self *Receipt) Cmp(other *Receipt) bool { + if bytes.Compare(self.PostState, other.PostState) != 0 { + return false + } + + return true +} + +// Transaction slice type for basic sorting +type Transactions []*Transaction + +func (s Transactions) Len() int { return len(s) } +func (s Transactions) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +type TxByNonce struct{ Transactions } + +func (s TxByNonce) Less(i, j int) bool { + return s.Transactions[i].Nonce < s.Transactions[j].Nonce +} diff --git a/ethchain/transaction_pool.go b/ethchain/transaction_pool.go new file mode 100644 index 000000000..063688aa8 --- /dev/null +++ b/ethchain/transaction_pool.go @@ -0,0 +1,224 @@ +package ethchain + +import ( + "bytes" + "container/list" + "fmt" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum/ethlog" + "github.com/ethereum/go-ethereum/ethstate" + "github.com/ethereum/go-ethereum/ethwire" +) + +var txplogger = ethlog.NewLogger("TXP") + +const ( + txPoolQueueSize = 50 +) + +type TxPoolHook chan *Transaction +type TxMsgTy byte + +const ( + TxPre = iota + TxPost + + minGasPrice = 1000000 +) + +var MinGasPrice = big.NewInt(10000000000000) + +type TxMsg struct { + Tx *Transaction + Type TxMsgTy +} + +func FindTx(pool *list.List, finder func(*Transaction, *list.Element) bool) *Transaction { + for e := pool.Front(); e != nil; e = e.Next() { + if tx, ok := e.Value.(*Transaction); ok { + if finder(tx, e) { + return tx + } + } + } + + return nil +} + +type TxProcessor interface { + ProcessTransaction(tx *Transaction) +} + +// The tx pool a thread safe transaction pool handler. In order to +// guarantee a non blocking pool we use a queue channel which can be +// independently read without needing access to the actual pool. If the +// pool is being drained or synced for whatever reason the transactions +// will simple queue up and handled when the mutex is freed. +type TxPool struct { + Ethereum EthManager + // The mutex for accessing the Tx pool. + mutex sync.Mutex + // Queueing channel for reading and writing incoming + // transactions to + queueChan chan *Transaction + // Quiting channel + quit chan bool + // The actual pool + pool *list.List + + SecondaryProcessor TxProcessor + + subscribers []chan TxMsg +} + +func NewTxPool(ethereum EthManager) *TxPool { + return &TxPool{ + pool: list.New(), + queueChan: make(chan *Transaction, txPoolQueueSize), + quit: make(chan bool), + Ethereum: ethereum, + } +} + +// Blocking function. Don't use directly. Use QueueTransaction instead +func (pool *TxPool) addTransaction(tx *Transaction) { + pool.mutex.Lock() + defer pool.mutex.Unlock() + + pool.pool.PushBack(tx) + + // Broadcast the transaction to the rest of the peers + pool.Ethereum.Broadcast(ethwire.MsgTxTy, []interface{}{tx.RlpData()}) +} + +func (pool *TxPool) ValidateTransaction(tx *Transaction) error { + // Get the last block so we can retrieve the sender and receiver from + // the merkle trie + block := pool.Ethereum.ChainManager().CurrentBlock + // Something has gone horribly wrong if this happens + if block == nil { + return fmt.Errorf("[TXPL] No last block on the block chain") + } + + if len(tx.Recipient) != 0 && len(tx.Recipient) != 20 { + return fmt.Errorf("[TXPL] Invalid recipient. len = %d", len(tx.Recipient)) + } + + if tx.GasPrice.Cmp(MinGasPrice) < 0 { + return fmt.Errorf("Gas price to low. Require %v > Got %v", MinGasPrice, tx.GasPrice) + } + + // Get the sender + //sender := pool.Ethereum.StateManager().procState.GetAccount(tx.Sender()) + sender := pool.Ethereum.StateManager().CurrentState().GetAccount(tx.Sender()) + + totAmount := new(big.Int).Set(tx.Value) + // Make sure there's enough in the sender's account. Having insufficient + // funds won't invalidate this transaction but simple ignores it. + if sender.Balance().Cmp(totAmount) < 0 { + return fmt.Errorf("[TXPL] Insufficient amount in sender's (%x) account", tx.Sender()) + } + + if tx.IsContract() { + if tx.GasPrice.Cmp(big.NewInt(minGasPrice)) < 0 { + return fmt.Errorf("[TXPL] Gasprice too low, %s given should be at least %d.", tx.GasPrice, minGasPrice) + } + } + + // Increment the nonce making each tx valid only once to prevent replay + // attacks + + return nil +} + +func (pool *TxPool) queueHandler() { +out: + for { + select { + case tx := <-pool.queueChan: + hash := tx.Hash() + foundTx := FindTx(pool.pool, func(tx *Transaction, e *list.Element) bool { + return bytes.Compare(tx.Hash(), hash) == 0 + }) + + if foundTx != nil { + break + } + + // Validate the transaction + err := pool.ValidateTransaction(tx) + if err != nil { + txplogger.Debugln("Validating Tx failed", err) + } else { + // Call blocking version. + pool.addTransaction(tx) + + tmp := make([]byte, 4) + copy(tmp, tx.Recipient) + + txplogger.Debugf("(t) %x => %x (%v) %x\n", tx.Sender()[:4], tmp, tx.Value, tx.Hash()) + + // Notify the subscribers + pool.Ethereum.EventMux().Post(TxEvent{TxPre, tx}) + } + case <-pool.quit: + break out + } + } +} + +func (pool *TxPool) QueueTransaction(tx *Transaction) { + pool.queueChan <- tx +} + +func (pool *TxPool) CurrentTransactions() []*Transaction { + pool.mutex.Lock() + defer pool.mutex.Unlock() + + txList := make([]*Transaction, pool.pool.Len()) + i := 0 + for e := pool.pool.Front(); e != nil; e = e.Next() { + tx := e.Value.(*Transaction) + + txList[i] = tx + + i++ + } + + return txList +} + +func (pool *TxPool) RemoveInvalid(state *ethstate.State) { + for e := pool.pool.Front(); e != nil; e = e.Next() { + tx := e.Value.(*Transaction) + sender := state.GetAccount(tx.Sender()) + err := pool.ValidateTransaction(tx) + if err != nil || sender.Nonce >= tx.Nonce { + pool.pool.Remove(e) + } + } +} + +func (pool *TxPool) Flush() []*Transaction { + txList := pool.CurrentTransactions() + + // Recreate a new list all together + // XXX Is this the fastest way? + pool.pool = list.New() + + return txList +} + +func (pool *TxPool) Start() { + go pool.queueHandler() +} + +func (pool *TxPool) Stop() { + close(pool.quit) + + pool.Flush() + + txplogger.Infoln("Stopped") +} diff --git a/ethchain/transaction_test.go b/ethchain/transaction_test.go new file mode 100644 index 000000000..3603fd8a7 --- /dev/null +++ b/ethchain/transaction_test.go @@ -0,0 +1 @@ +package ethchain diff --git a/ethchain/types.go b/ethchain/types.go new file mode 100644 index 000000000..29084c749 --- /dev/null +++ b/ethchain/types.go @@ -0,0 +1,312 @@ +package ethchain + +import ( + "fmt" +) + +type OpCode int + +// Op codes +const ( + // 0x0 range - arithmetic ops + STOP = 0x00 + ADD = 0x01 + MUL = 0x02 + SUB = 0x03 + DIV = 0x04 + SDIV = 0x05 + MOD = 0x06 + SMOD = 0x07 + EXP = 0x08 + NEG = 0x09 + LT = 0x0a + GT = 0x0b + SLT = 0x0c + SGT = 0x0d + EQ = 0x0e + NOT = 0x0f + + // 0x10 range - bit ops + AND = 0x10 + OR = 0x11 + XOR = 0x12 + BYTE = 0x13 + ADDMOD = 0x14 + MULMOD = 0x15 + + // 0x20 range - crypto + SHA3 = 0x20 + + // 0x30 range - closure state + ADDRESS = 0x30 + BALANCE = 0x31 + ORIGIN = 0x32 + CALLER = 0x33 + CALLVALUE = 0x34 + CALLDATALOAD = 0x35 + CALLDATASIZE = 0x36 + CALLDATACOPY = 0x37 + CODESIZE = 0x38 + CODECOPY = 0x39 + GASPRICE = 0x3a + EXTCODESIZE = 0x3b + EXTCODECOPY = 0x3c + + // 0x40 range - block operations + PREVHASH = 0x40 + COINBASE = 0x41 + TIMESTAMP = 0x42 + NUMBER = 0x43 + DIFFICULTY = 0x44 + GASLIMIT = 0x45 + + // 0x50 range - 'storage' and execution + POP = 0x50 + //DUP = 0x51 + //SWAP = 0x52 + MLOAD = 0x53 + MSTORE = 0x54 + MSTORE8 = 0x55 + SLOAD = 0x56 + SSTORE = 0x57 + JUMP = 0x58 + JUMPI = 0x59 + PC = 0x5a + MSIZE = 0x5b + GAS = 0x5c + + // 0x60 range + PUSH1 = 0x60 + PUSH2 = 0x61 + PUSH3 = 0x62 + PUSH4 = 0x63 + PUSH5 = 0x64 + PUSH6 = 0x65 + PUSH7 = 0x66 + PUSH8 = 0x67 + PUSH9 = 0x68 + PUSH10 = 0x69 + PUSH11 = 0x6a + PUSH12 = 0x6b + PUSH13 = 0x6c + PUSH14 = 0x6d + PUSH15 = 0x6e + PUSH16 = 0x6f + PUSH17 = 0x70 + PUSH18 = 0x71 + PUSH19 = 0x72 + PUSH20 = 0x73 + PUSH21 = 0x74 + PUSH22 = 0x75 + PUSH23 = 0x76 + PUSH24 = 0x77 + PUSH25 = 0x78 + PUSH26 = 0x79 + PUSH27 = 0x7a + PUSH28 = 0x7b + PUSH29 = 0x7c + PUSH30 = 0x7d + PUSH31 = 0x7e + PUSH32 = 0x7f + + DUP1 = 0x80 + DUP2 = 0x81 + DUP3 = 0x82 + DUP4 = 0x83 + DUP5 = 0x84 + DUP6 = 0x85 + DUP7 = 0x86 + DUP8 = 0x87 + DUP9 = 0x88 + DUP10 = 0x89 + DUP11 = 0x8a + DUP12 = 0x8b + DUP13 = 0x8c + DUP14 = 0x8d + DUP15 = 0x8e + DUP16 = 0x8f + + SWAP1 = 0x90 + SWAP2 = 0x91 + SWAP3 = 0x92 + SWAP4 = 0x93 + SWAP5 = 0x94 + SWAP6 = 0x95 + SWAP7 = 0x96 + SWAP8 = 0x97 + SWAP9 = 0x98 + SWAP10 = 0x99 + SWAP11 = 0x9a + SWAP12 = 0x9b + SWAP13 = 0x9c + SWAP14 = 0x9d + SWAP15 = 0x9e + SWAP16 = 0x9f + + // 0xf0 range - closures + CREATE = 0xf0 + CALL = 0xf1 + RETURN = 0xf2 + CALLCODE = 0xf3 + + // 0x70 range - other + LOG = 0xfe // XXX Unofficial + SUICIDE = 0xff +) + +// Since the opcodes aren't all in order we can't use a regular slice +var opCodeToString = map[OpCode]string{ + // 0x0 range - arithmetic ops + STOP: "STOP", + ADD: "ADD", + MUL: "MUL", + SUB: "SUB", + DIV: "DIV", + SDIV: "SDIV", + MOD: "MOD", + SMOD: "SMOD", + EXP: "EXP", + NEG: "NEG", + LT: "LT", + GT: "GT", + SLT: "SLT", + SGT: "SGT", + EQ: "EQ", + NOT: "NOT", + + // 0x10 range - bit ops + AND: "AND", + OR: "OR", + XOR: "XOR", + BYTE: "BYTE", + ADDMOD: "ADDMOD", + MULMOD: "MULMOD", + + // 0x20 range - crypto + SHA3: "SHA3", + + // 0x30 range - closure state + ADDRESS: "ADDRESS", + BALANCE: "BALANCE", + ORIGIN: "ORIGIN", + CALLER: "CALLER", + CALLVALUE: "CALLVALUE", + CALLDATALOAD: "CALLDATALOAD", + CALLDATASIZE: "CALLDATASIZE", + CALLDATACOPY: "CALLDATACOPY", + CODESIZE: "CODESIZE", + CODECOPY: "CODECOPY", + GASPRICE: "TXGASPRICE", + + // 0x40 range - block operations + PREVHASH: "PREVHASH", + COINBASE: "COINBASE", + TIMESTAMP: "TIMESTAMP", + NUMBER: "NUMBER", + DIFFICULTY: "DIFFICULTY", + GASLIMIT: "GASLIMIT", + EXTCODESIZE: "EXTCODESIZE", + EXTCODECOPY: "EXTCODECOPY", + + // 0x50 range - 'storage' and execution + POP: "POP", + //DUP: "DUP", + //SWAP: "SWAP", + MLOAD: "MLOAD", + MSTORE: "MSTORE", + MSTORE8: "MSTORE8", + SLOAD: "SLOAD", + SSTORE: "SSTORE", + JUMP: "JUMP", + JUMPI: "JUMPI", + PC: "PC", + MSIZE: "MSIZE", + GAS: "GAS", + + // 0x60 range - push + PUSH1: "PUSH1", + PUSH2: "PUSH2", + PUSH3: "PUSH3", + PUSH4: "PUSH4", + PUSH5: "PUSH5", + PUSH6: "PUSH6", + PUSH7: "PUSH7", + PUSH8: "PUSH8", + PUSH9: "PUSH9", + PUSH10: "PUSH10", + PUSH11: "PUSH11", + PUSH12: "PUSH12", + PUSH13: "PUSH13", + PUSH14: "PUSH14", + PUSH15: "PUSH15", + PUSH16: "PUSH16", + PUSH17: "PUSH17", + PUSH18: "PUSH18", + PUSH19: "PUSH19", + PUSH20: "PUSH20", + PUSH21: "PUSH21", + PUSH22: "PUSH22", + PUSH23: "PUSH23", + PUSH24: "PUSH24", + PUSH25: "PUSH25", + PUSH26: "PUSH26", + PUSH27: "PUSH27", + PUSH28: "PUSH28", + PUSH29: "PUSH29", + PUSH30: "PUSH30", + PUSH31: "PUSH31", + PUSH32: "PUSH32", + + DUP1: "DUP1", + DUP2: "DUP2", + DUP3: "DUP3", + DUP4: "DUP4", + DUP5: "DUP5", + DUP6: "DUP6", + DUP7: "DUP7", + DUP8: "DUP8", + DUP9: "DUP9", + DUP10: "DUP10", + DUP11: "DUP11", + DUP12: "DUP12", + DUP13: "DUP13", + DUP14: "DUP14", + DUP15: "DUP15", + DUP16: "DUP16", + + SWAP1: "SWAP1", + SWAP2: "SWAP2", + SWAP3: "SWAP3", + SWAP4: "SWAP4", + SWAP5: "SWAP5", + SWAP6: "SWAP6", + SWAP7: "SWAP7", + SWAP8: "SWAP8", + SWAP9: "SWAP9", + SWAP10: "SWAP10", + SWAP11: "SWAP11", + SWAP12: "SWAP12", + SWAP13: "SWAP13", + SWAP14: "SWAP14", + SWAP15: "SWAP15", + SWAP16: "SWAP16", + + // 0xf0 range + CREATE: "CREATE", + CALL: "CALL", + RETURN: "RETURN", + CALLCODE: "CALLCODE", + + // 0x70 range - other + LOG: "LOG", + SUICIDE: "SUICIDE", +} + +func (o OpCode) String() string { + str := opCodeToString[o] + if len(str) == 0 { + return fmt.Sprintf("Missing opcode 0x%x", int(o)) + } + + return str +} diff --git a/ethchain/vm_env.go b/ethchain/vm_env.go new file mode 100644 index 000000000..6dda27c6f --- /dev/null +++ b/ethchain/vm_env.go @@ -0,0 +1,36 @@ +package ethchain + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/ethstate" + "github.com/ethereum/go-ethereum/vm" +) + +type VMEnv struct { + state *ethstate.State + block *Block + tx *Transaction +} + +func NewEnv(state *ethstate.State, tx *Transaction, block *Block) *VMEnv { + return &VMEnv{ + state: state, + block: block, + tx: tx, + } +} + +func (self *VMEnv) Origin() []byte { return self.tx.Sender() } +func (self *VMEnv) BlockNumber() *big.Int { return self.block.Number } +func (self *VMEnv) PrevHash() []byte { return self.block.PrevHash } +func (self *VMEnv) Coinbase() []byte { return self.block.Coinbase } +func (self *VMEnv) Time() int64 { return self.block.Time } +func (self *VMEnv) Difficulty() *big.Int { return self.block.Difficulty } +func (self *VMEnv) BlockHash() []byte { return self.block.Hash() } +func (self *VMEnv) Value() *big.Int { return self.tx.Value } +func (self *VMEnv) State() *ethstate.State { return self.state } +func (self *VMEnv) GasLimit() *big.Int { return self.block.GasLimit } +func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) error { + return vm.Transfer(from, to, amount) +} |