mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2024-12-25 13:07:17 +00:00
b5b83db450
This also changes the chain export format so there is no enclosing list around the blocks, which enables streaming export.
517 lines
13 KiB
Go
517 lines
13 KiB
Go
package core
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"sync"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/event"
|
|
"github.com/ethereum/go-ethereum/logger"
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
"github.com/ethereum/go-ethereum/state"
|
|
)
|
|
|
|
var (
|
|
chainlogger = logger.NewLogger("CHAIN")
|
|
jsonlogger = logger.NewJsonLogger()
|
|
|
|
blockHashPre = []byte("block-hash-")
|
|
blockNumPre = []byte("block-num-")
|
|
)
|
|
|
|
type StateQuery interface {
|
|
GetAccount(addr []byte) *state.StateObject
|
|
}
|
|
|
|
func CalcDifficulty(block, parent *types.Header) *big.Int {
|
|
diff := new(big.Int)
|
|
|
|
min := big.NewInt(2048)
|
|
adjust := new(big.Int).Div(parent.Difficulty, min)
|
|
if (block.Time - parent.Time) < 8 {
|
|
diff.Add(parent.Difficulty, adjust)
|
|
} else {
|
|
diff.Sub(parent.Difficulty, adjust)
|
|
}
|
|
|
|
if diff.Cmp(GenesisDiff) < 0 {
|
|
return GenesisDiff
|
|
}
|
|
|
|
return diff
|
|
}
|
|
|
|
func CalculateTD(block, parent *types.Block) *big.Int {
|
|
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(parent.Td, uncleDiff)
|
|
td = td.Add(td, block.Header().Difficulty)
|
|
|
|
return td
|
|
}
|
|
|
|
func CalcGasLimit(parent, block *types.Block) *big.Int {
|
|
if block.Number().Cmp(big.NewInt(0)) == 0 {
|
|
return common.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))
|
|
|
|
return common.BigMax(GenesisGasLimit, result)
|
|
}
|
|
|
|
type ChainManager struct {
|
|
//eth EthManager
|
|
blockDb common.Database
|
|
stateDb common.Database
|
|
processor types.BlockProcessor
|
|
eventMux *event.TypeMux
|
|
genesisBlock *types.Block
|
|
// Last known total difficulty
|
|
mu sync.RWMutex
|
|
tsmu sync.RWMutex
|
|
td *big.Int
|
|
currentBlock *types.Block
|
|
lastBlockHash common.Hash
|
|
|
|
transState *state.StateDB
|
|
txState *state.ManagedState
|
|
|
|
quit chan struct{}
|
|
}
|
|
|
|
func NewChainManager(blockDb, stateDb common.Database, mux *event.TypeMux) *ChainManager {
|
|
bc := &ChainManager{blockDb: blockDb, stateDb: stateDb, genesisBlock: GenesisBlock(stateDb), eventMux: mux, quit: make(chan struct{})}
|
|
bc.setLastBlock()
|
|
bc.transState = bc.State().Copy()
|
|
// Take ownership of this particular state
|
|
bc.txState = state.ManageState(bc.State().Copy())
|
|
go bc.update()
|
|
|
|
return bc
|
|
}
|
|
|
|
func (self *ChainManager) Td() *big.Int {
|
|
self.mu.RLock()
|
|
defer self.mu.RUnlock()
|
|
|
|
return self.td
|
|
}
|
|
|
|
func (self *ChainManager) LastBlockHash() common.Hash {
|
|
self.mu.RLock()
|
|
defer self.mu.RUnlock()
|
|
|
|
return self.lastBlockHash
|
|
}
|
|
|
|
func (self *ChainManager) CurrentBlock() *types.Block {
|
|
self.mu.RLock()
|
|
defer self.mu.RUnlock()
|
|
|
|
return self.currentBlock
|
|
}
|
|
|
|
func (self *ChainManager) Status() (td *big.Int, currentBlock common.Hash, genesisBlock common.Hash) {
|
|
self.mu.RLock()
|
|
defer self.mu.RUnlock()
|
|
|
|
return self.td, self.currentBlock.Hash(), self.genesisBlock.Hash()
|
|
}
|
|
|
|
func (self *ChainManager) SetProcessor(proc types.BlockProcessor) {
|
|
self.processor = proc
|
|
}
|
|
|
|
func (self *ChainManager) State() *state.StateDB {
|
|
return state.New(self.CurrentBlock().Root(), self.stateDb)
|
|
}
|
|
|
|
func (self *ChainManager) TransState() *state.StateDB {
|
|
self.tsmu.RLock()
|
|
defer self.tsmu.RUnlock()
|
|
|
|
return self.transState
|
|
}
|
|
|
|
func (self *ChainManager) TxState() *state.ManagedState {
|
|
self.tsmu.RLock()
|
|
defer self.tsmu.RUnlock()
|
|
|
|
return self.txState
|
|
}
|
|
|
|
func (self *ChainManager) setTxState(statedb *state.StateDB) {
|
|
self.tsmu.Lock()
|
|
defer self.tsmu.Unlock()
|
|
self.txState = state.ManageState(statedb)
|
|
}
|
|
|
|
func (self *ChainManager) setTransState(statedb *state.StateDB) {
|
|
self.transState = statedb
|
|
}
|
|
|
|
func (bc *ChainManager) setLastBlock() {
|
|
data, _ := bc.blockDb.Get([]byte("LastBlock"))
|
|
if len(data) != 0 {
|
|
block := bc.GetBlock(common.BytesToHash(data))
|
|
bc.currentBlock = block
|
|
bc.lastBlockHash = block.Hash()
|
|
|
|
// Set the last know difficulty (might be 0x0 as initial value, Genesis)
|
|
bc.td = common.BigD(bc.blockDb.LastKnownTD())
|
|
} else {
|
|
bc.Reset()
|
|
}
|
|
|
|
chainlogger.Infof("Last block (#%v) %x TD=%v\n", bc.currentBlock.Number(), bc.currentBlock.Hash(), bc.td)
|
|
}
|
|
|
|
// Block creation & chain handling
|
|
func (bc *ChainManager) NewBlock(coinbase common.Address) *types.Block {
|
|
bc.mu.RLock()
|
|
defer bc.mu.RUnlock()
|
|
|
|
var (
|
|
root common.Hash
|
|
parentHash common.Hash
|
|
)
|
|
|
|
if bc.currentBlock != nil {
|
|
root = bc.currentBlock.Header().Root
|
|
parentHash = bc.lastBlockHash
|
|
}
|
|
|
|
block := types.NewBlock(
|
|
parentHash,
|
|
coinbase,
|
|
root,
|
|
common.BigPow(2, 32),
|
|
0,
|
|
"")
|
|
block.SetUncles(nil)
|
|
block.SetTransactions(nil)
|
|
block.SetReceipts(nil)
|
|
|
|
parent := bc.currentBlock
|
|
if parent != nil {
|
|
header := block.Header()
|
|
header.Difficulty = CalcDifficulty(block.Header(), parent.Header())
|
|
header.Number = new(big.Int).Add(parent.Header().Number, common.Big1)
|
|
header.GasLimit = CalcGasLimit(parent, block)
|
|
|
|
}
|
|
|
|
return block
|
|
}
|
|
|
|
func (bc *ChainManager) Reset() {
|
|
bc.mu.Lock()
|
|
defer bc.mu.Unlock()
|
|
|
|
for block := bc.currentBlock; block != nil; block = bc.GetBlock(block.Header().ParentHash) {
|
|
bc.removeBlock(block)
|
|
}
|
|
|
|
// Prepare the genesis block
|
|
bc.write(bc.genesisBlock)
|
|
bc.insert(bc.genesisBlock)
|
|
bc.currentBlock = bc.genesisBlock
|
|
|
|
bc.setTotalDifficulty(common.Big("0"))
|
|
}
|
|
|
|
func (bc *ChainManager) removeBlock(block *types.Block) {
|
|
bc.blockDb.Delete(append(blockHashPre, block.Hash().Bytes()...))
|
|
}
|
|
|
|
func (bc *ChainManager) ResetWithGenesisBlock(gb *types.Block) {
|
|
bc.mu.Lock()
|
|
defer bc.mu.Unlock()
|
|
|
|
for block := bc.currentBlock; block != nil; block = bc.GetBlock(block.Header().ParentHash) {
|
|
bc.removeBlock(block)
|
|
}
|
|
|
|
// Prepare the genesis block
|
|
bc.genesisBlock = gb
|
|
bc.write(bc.genesisBlock)
|
|
bc.insert(bc.genesisBlock)
|
|
bc.currentBlock = bc.genesisBlock
|
|
}
|
|
|
|
// Export writes the active chain to the given writer.
|
|
func (self *ChainManager) Export(w io.Writer) error {
|
|
self.mu.RLock()
|
|
defer self.mu.RUnlock()
|
|
chainlogger.Infof("exporting %v blocks...\n", self.currentBlock.Header().Number)
|
|
for block := self.currentBlock; block != nil; block = self.GetBlock(block.Header().ParentHash) {
|
|
if err := block.EncodeRLP(w); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (bc *ChainManager) insert(block *types.Block) {
|
|
bc.blockDb.Put([]byte("LastBlock"), block.Hash().Bytes())
|
|
bc.currentBlock = block
|
|
bc.lastBlockHash = block.Hash()
|
|
|
|
key := append(blockNumPre, block.Number().Bytes()...)
|
|
bc.blockDb.Put(key, bc.lastBlockHash.Bytes())
|
|
}
|
|
|
|
func (bc *ChainManager) write(block *types.Block) {
|
|
enc, _ := rlp.EncodeToBytes((*types.StorageBlock)(block))
|
|
key := append(blockHashPre, block.Hash().Bytes()...)
|
|
bc.blockDb.Put(key, enc)
|
|
}
|
|
|
|
// Accessors
|
|
func (bc *ChainManager) Genesis() *types.Block {
|
|
return bc.genesisBlock
|
|
}
|
|
|
|
// Block fetching methods
|
|
func (bc *ChainManager) HasBlock(hash common.Hash) bool {
|
|
data, _ := bc.blockDb.Get(append(blockHashPre, hash[:]...))
|
|
return len(data) != 0
|
|
}
|
|
|
|
func (self *ChainManager) GetBlockHashesFromHash(hash common.Hash, max uint64) (chain []common.Hash) {
|
|
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++ {
|
|
parentHash := block.Header().ParentHash
|
|
block = self.GetBlock(parentHash)
|
|
if block == nil {
|
|
chainlogger.Infof("GetBlockHashesFromHash Parent UNKNOWN %x\n", parentHash)
|
|
break
|
|
}
|
|
|
|
chain = append(chain, block.Hash())
|
|
if block.Header().Number.Cmp(common.Big0) <= 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (self *ChainManager) GetBlock(hash common.Hash) *types.Block {
|
|
data, _ := self.blockDb.Get(append(blockHashPre, hash[:]...))
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
var block types.StorageBlock
|
|
if err := rlp.Decode(bytes.NewReader(data), &block); err != nil {
|
|
chainlogger.Errorf("invalid block RLP for hash %x: %v", hash, err)
|
|
return nil
|
|
}
|
|
return (*types.Block)(&block)
|
|
}
|
|
|
|
func (self *ChainManager) GetBlockByNumber(num uint64) *types.Block {
|
|
self.mu.RLock()
|
|
defer self.mu.RUnlock()
|
|
|
|
key, _ := self.blockDb.Get(append(blockNumPre, big.NewInt(int64(num)).Bytes()...))
|
|
if len(key) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return self.GetBlock(common.BytesToHash(key))
|
|
}
|
|
|
|
func (self *ChainManager) GetUnclesInChain(block *types.Block, length int) (uncles []*types.Header) {
|
|
for i := 0; block != nil && i < length; i++ {
|
|
uncles = append(uncles, block.Uncles()...)
|
|
block = self.GetBlock(block.ParentHash())
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (self *ChainManager) GetAncestors(block *types.Block, length int) (blocks []*types.Block) {
|
|
for i := 0; i < length; i++ {
|
|
block = self.GetBlock(block.ParentHash())
|
|
if block == nil {
|
|
break
|
|
}
|
|
|
|
blocks = append(blocks, block)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (bc *ChainManager) setTotalDifficulty(td *big.Int) {
|
|
bc.blockDb.Put([]byte("LTD"), td.Bytes())
|
|
bc.td = td
|
|
}
|
|
|
|
func (self *ChainManager) CalcTotalDiff(block *types.Block) (*big.Int, error) {
|
|
parent := self.GetBlock(block.Header().ParentHash)
|
|
if parent == nil {
|
|
return nil, fmt.Errorf("Unable to calculate total diff without known parent %x", block.Header().ParentHash)
|
|
}
|
|
|
|
parentTd := parent.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.Header().Difficulty)
|
|
|
|
return td, nil
|
|
}
|
|
|
|
func (bc *ChainManager) Stop() {
|
|
close(bc.quit)
|
|
}
|
|
|
|
type queueEvent struct {
|
|
queue []interface{}
|
|
canonicalCount int
|
|
sideCount int
|
|
splitCount int
|
|
}
|
|
|
|
func (self *ChainManager) InsertChain(chain types.Blocks) error {
|
|
//self.tsmu.Lock()
|
|
//defer self.tsmu.Unlock()
|
|
|
|
// A queued approach to delivering events. This is generally faster than direct delivery and requires much less mutex acquiring.
|
|
var queue = make([]interface{}, len(chain))
|
|
var queueEvent = queueEvent{queue: queue}
|
|
for i, block := range chain {
|
|
// Call in to the block processor and check for errors. It's likely that if one block fails
|
|
// all others will fail too (unless a known block is returned).
|
|
td, err := self.processor.Process(block)
|
|
if err != nil {
|
|
if IsKnownBlockErr(err) {
|
|
continue
|
|
}
|
|
|
|
h := block.Header()
|
|
chainlogger.Infof("INVALID block #%v (%x)\n", h.Number, h.Hash().Bytes()[:4])
|
|
chainlogger.Infoln(err)
|
|
chainlogger.Debugln(block)
|
|
return err
|
|
}
|
|
block.Td = td
|
|
|
|
self.mu.Lock()
|
|
cblock := self.currentBlock
|
|
{
|
|
// Write block to database. Eventually we'll have to improve on this and throw away blocks that are
|
|
// not in the canonical chain.
|
|
self.write(block)
|
|
// Compare the TD of the last known block in the canonical chain to make sure it's greater.
|
|
// At this point it's possible that a different chain (fork) becomes the new canonical chain.
|
|
if td.Cmp(self.td) > 0 {
|
|
if block.Header().Number.Cmp(new(big.Int).Add(cblock.Header().Number, common.Big1)) < 0 {
|
|
chash := cblock.Hash()
|
|
hash := block.Hash()
|
|
chainlogger.Infof("Split detected. New head #%v (%x) TD=%v, was #%v (%x) TD=%v\n", block.Header().Number, hash[:4], td, cblock.Header().Number, chash[:4], self.td)
|
|
|
|
queue[i] = ChainSplitEvent{block}
|
|
queueEvent.splitCount++
|
|
}
|
|
|
|
self.setTotalDifficulty(td)
|
|
self.insert(block)
|
|
|
|
/* XXX crashes
|
|
jsonlogger.LogJson(&logger.EthChainNewHead{
|
|
BlockHash: common.Bytes2Hex(block.Hash()),
|
|
BlockNumber: block.Number(),
|
|
ChainHeadHash: common.Bytes2Hex(cblock.Hash()),
|
|
BlockPrevHash: common.Bytes2Hex(block.ParentHash()),
|
|
})
|
|
*/
|
|
|
|
self.setTransState(state.New(block.Root(), self.stateDb))
|
|
self.setTxState(state.New(block.Root(), self.stateDb))
|
|
|
|
queue[i] = ChainEvent{block}
|
|
queueEvent.canonicalCount++
|
|
} else {
|
|
queue[i] = ChainSideEvent{block}
|
|
queueEvent.sideCount++
|
|
}
|
|
}
|
|
self.mu.Unlock()
|
|
|
|
}
|
|
|
|
// XXX put this in a goroutine?
|
|
go self.eventMux.Post(queueEvent)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *ChainManager) update() {
|
|
events := self.eventMux.Subscribe(queueEvent{})
|
|
|
|
out:
|
|
for {
|
|
select {
|
|
case ev := <-events.Chan():
|
|
switch ev := ev.(type) {
|
|
case queueEvent:
|
|
for i, event := range ev.queue {
|
|
switch event := event.(type) {
|
|
case ChainEvent:
|
|
// We need some control over the mining operation. Acquiring locks and waiting for the miner to create new block takes too long
|
|
// and in most cases isn't even necessary.
|
|
if i == ev.canonicalCount {
|
|
self.eventMux.Post(ChainHeadEvent{event.Block})
|
|
}
|
|
case ChainSplitEvent:
|
|
// On chain splits we need to reset the transaction state. We can't be sure whether the actual
|
|
// state of the accounts are still valid.
|
|
if i == ev.splitCount {
|
|
self.setTxState(state.New(event.Block.Root(), self.stateDb))
|
|
}
|
|
}
|
|
|
|
self.eventMux.Post(event)
|
|
}
|
|
}
|
|
case <-self.quit:
|
|
break out
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
// Satisfy state query interface
|
|
func (self *ChainManager) GetAccount(addr common.Hash) *state.StateObject {
|
|
return self.State().GetAccount(addr)
|
|
}
|
|
*/
|