Skip to content

Commit

Permalink
stateroot: move state-root related logic to core/stateroot
Browse files Browse the repository at this point in the history
  • Loading branch information
fyrchik committed Mar 9, 2021
1 parent 7a17672 commit bf20db0
Show file tree
Hide file tree
Showing 19 changed files with 245 additions and 382 deletions.
14 changes: 2 additions & 12 deletions internal/fakechain/fakechain.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,6 @@ func (chain *FakeChain) AddBlock(block *block.Block) error {
return nil
}

// AddStateRoot implements Blockchainer interface.
func (chain *FakeChain) AddStateRoot(r *state.MPTRoot) error {
panic("TODO")
}

// BlockHeight implements Feer interface.
func (chain *FakeChain) BlockHeight() uint32 {
return atomic.LoadUint32(&chain.Blockheight)
Expand Down Expand Up @@ -279,13 +274,8 @@ func (chain *FakeChain) GetEnrollments() ([]state.Validator, error) {
panic("TODO")
}

// GetStateProof implements Blockchainer interface.
func (chain *FakeChain) GetStateProof(util.Uint256, []byte) ([][]byte, error) {
panic("TODO")
}

// GetStateRoot implements Blockchainer interface.
func (chain *FakeChain) GetStateRoot(height uint32) (*state.MPTRootState, error) {
// GetStateModule implements Blockchainer interface.
func (chain *FakeChain) GetStateModule() blockchainer.StateRoot {
panic("TODO")
}

Expand Down
6 changes: 3 additions & 3 deletions pkg/consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ func (s *service) newPrepareRequest() payload.PrepareRequest {
r := new(prepareRequest)
if s.stateRootEnabled {
r.stateRootEnabled = true
if sr, err := s.Chain.GetStateRoot(s.dbft.BlockIndex - 1); err == nil {
if sr, err := s.Chain.GetStateModule().GetStateRoot(s.dbft.BlockIndex - 1); err == nil {
r.stateRoot = sr.Root
} else {
panic(err)
Expand Down Expand Up @@ -483,7 +483,7 @@ func (s *service) verifyRequest(p payload.ConsensusPayload) error {
return errInvalidVersion
}
if s.stateRootEnabled {
sr, err := s.Chain.GetStateRoot(s.dbft.BlockIndex - 1)
sr, err := s.Chain.GetStateModule().GetStateRoot(s.dbft.BlockIndex - 1)
if err != nil {
return err
} else if sr.Root != req.stateRoot {
Expand Down Expand Up @@ -637,7 +637,7 @@ func (s *service) newBlockFromContext(ctx *dbft.Context) block.Block {
block.Block.Timestamp = ctx.Timestamp / nsInMs
block.Block.Index = ctx.BlockIndex
if s.stateRootEnabled {
sr, err := s.Chain.GetStateRoot(ctx.BlockIndex - 1)
sr, err := s.Chain.GetStateModule().GetStateRoot(ctx.BlockIndex - 1)
if err != nil {
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/consensus/consensus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ func TestService_PrepareRequest(t *testing.T) {
prevHash: prevHash,
})

sr, err := srv.Chain.GetStateRoot(srv.dbft.BlockIndex - 1)
sr, err := srv.Chain.GetStateModule().GetStateRoot(srv.dbft.BlockIndex - 1)
require.NoError(t, err)
checkRequest(t, nil, &prepareRequest{
stateRootEnabled: true,
Expand Down
129 changes: 14 additions & 115 deletions pkg/core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
"github.com/nspcc-dev/neo-go/pkg/core/mpt"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/stateroot"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto"
Expand Down Expand Up @@ -134,6 +134,8 @@ type Blockchain struct {

extensible atomic.Value

stateRoot *stateroot.Module

// Notification subsystem.
events chan bcEvent
subCh chan interface{}
Expand Down Expand Up @@ -193,6 +195,8 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
contracts: *native.NewContracts(cfg.P2PSigExtensions),
}

bc.stateRoot = stateroot.NewModule(bc, bc.log, bc.dao.Store)

if err := bc.init(); err != nil {
return nil, err
}
Expand Down Expand Up @@ -237,7 +241,7 @@ func (bc *Blockchain) init() error {
if err != nil {
return err
}
if err := bc.dao.InitMPT(0, bc.config.KeepOnlyLatestState); err != nil {
if err := bc.stateRoot.Init(0, bc.config.KeepOnlyLatestState); err != nil {
return fmt.Errorf("can't init MPT: %w", err)
}
return bc.storeBlock(genesisBlock, nil)
Expand All @@ -257,7 +261,7 @@ func (bc *Blockchain) init() error {
}
bc.blockHeight = bHeight
bc.persistedHeight = bHeight
if err = bc.dao.InitMPT(bHeight, bc.config.KeepOnlyLatestState); err != nil {
if err = bc.stateRoot.Init(bHeight, bc.config.KeepOnlyLatestState); err != nil {
return fmt.Errorf("can't init MPT at height %d: %w", bHeight, err)
}

Expand Down Expand Up @@ -479,7 +483,7 @@ func (bc *Blockchain) AddBlock(block *block.Block) error {
ErrHdrStateRootSetting, bc.config.StateRootInHeader, block.StateRootEnabled)
}
if bc.config.StateRootInHeader {
if sr := bc.dao.MPT.StateRoot(); block.PrevStateRoot != sr {
if sr := bc.stateRoot.CurrentLocalStateRoot(); block.PrevStateRoot != sr {
return fmt.Errorf("%w: %s != %s",
ErrHdrInvalidStateRoot, block.PrevStateRoot.StringLE(), sr.StringLE())
}
Expand Down Expand Up @@ -606,15 +610,9 @@ func (bc *Blockchain) addHeaders(verify bool, headers ...*block.Header) error {
return nil
}

// GetStateProof returns proof of having key in the MPT with the specified root.
func (bc *Blockchain) GetStateProof(root util.Uint256, key []byte) ([][]byte, error) {
tr := mpt.NewTrie(mpt.NewHashNode(root), false, storage.NewMemCachedStore(bc.dao.Store))
return tr.GetProof(key)
}

// GetStateRoot returns state root for a given height.
func (bc *Blockchain) GetStateRoot(height uint32) (*state.MPTRootState, error) {
return bc.dao.GetStateRoot(height)
// GetStateModule returns state root service instance.
func (bc *Blockchain) GetStateModule() blockchainer.StateRoot {
return bc.stateRoot
}

// storeBlock performs chain update using the block given, it executes all
Expand Down Expand Up @@ -718,33 +716,14 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
writeBuf.Reset()

d := cache.DAO.(*dao.Simple)
if err := d.UpdateMPT(); err != nil {
b := d.GetMPTBatch()
if err := bc.stateRoot.AddMPTBatch(block.Index, b); err != nil {
// Here MPT can be left in a half-applied state.
// However if this error occurs, this is a bug somewhere in code
// because changes applied are the ones from HALTed transactions.
return fmt.Errorf("error while trying to apply MPT changes: %w", err)
}

root := d.MPT.StateRoot()
var prevHash util.Uint256
if block.Index > 0 {
prev, err := bc.dao.GetStateRoot(block.Index - 1)
if err != nil {
return fmt.Errorf("can't get previous state root: %w", err)
}
prevHash = hash.DoubleSha256(prev.GetSignedPart())
}
err = bc.AddStateRoot(&state.MPTRoot{
MPTRootBase: state.MPTRootBase{
Index: block.Index,
PrevHash: prevHash,
Root: root,
},
})
if err != nil {
return err
}

if bc.config.SaveStorageBatch {
bc.lastBatch = cache.DAO.GetBatch()
}
Expand All @@ -767,13 +746,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
bc.lock.Unlock()
return err
}
bc.dao.MPT.Flush()
// Every persist cycle we also compact our in-memory MPT.
persistedHeight := atomic.LoadUint32(&bc.persistedHeight)
if persistedHeight == block.Index-1 {
// 10 is good and roughly estimated to fit remaining trie into 1M of memory.
bc.dao.MPT.Collapse(10)
}

bc.topBlock.Store(block)
atomic.StoreUint32(&bc.blockHeight, block.Index)
bc.memPool.RemoveStale(func(tx *transaction.Transaction) bool { return bc.IsTxStillRelevant(tx, txpool, false) }, bc)
Expand Down Expand Up @@ -1582,79 +1555,6 @@ func (bc *Blockchain) IsTxStillRelevant(t *transaction.Transaction, txpool *memp

}

// AddStateRoot add new (possibly unverified) state root to the blockchain.
func (bc *Blockchain) AddStateRoot(r *state.MPTRoot) error {
our, err := bc.GetStateRoot(r.Index)
if err == nil {
if our.Flag == state.Verified {
return bc.updateStateHeight(r.Index)
} else if r.Witness == nil && our.Witness != nil {
r.Witness = our.Witness
}
}
if err := bc.verifyStateRoot(r); err != nil {
return fmt.Errorf("invalid state root: %w", err)
}
if r.Index > bc.BlockHeight() { // just put it into the store for future checks
return bc.dao.PutStateRoot(&state.MPTRootState{
MPTRoot: *r,
Flag: state.Unverified,
})
}

flag := state.Unverified
if r.Witness != nil {
if err := bc.verifyStateRootWitness(r); err != nil {
return fmt.Errorf("can't verify signature: %w", err)
}
flag = state.Verified
}
err = bc.dao.PutStateRoot(&state.MPTRootState{
MPTRoot: *r,
Flag: flag,
})
if err != nil {
return err
}
return bc.updateStateHeight(r.Index)
}

func (bc *Blockchain) updateStateHeight(newHeight uint32) error {
h, err := bc.dao.GetCurrentStateRootHeight()
if err != nil {
return fmt.Errorf("can't get current state root height: %w", err)
} else if newHeight == h+1 {
updateStateHeightMetric(newHeight)
return bc.dao.PutCurrentStateRootHeight(h + 1)
}
return nil
}

// verifyStateRoot checks if state root is valid.
func (bc *Blockchain) verifyStateRoot(r *state.MPTRoot) error {
if r.Index == 0 {
return nil
}
prev, err := bc.GetStateRoot(r.Index - 1)
if err != nil {
return errors.New("can't get previous state root")
} else if !r.PrevHash.Equals(hash.DoubleSha256(prev.GetSignedPart())) {
return errors.New("previous hash mismatch")
} else if prev.Version != r.Version {
return errors.New("version mismatch")
}
return nil
}

// verifyStateRootWitness verifies that state root signature is correct.
func (bc *Blockchain) verifyStateRootWitness(r *state.MPTRoot) error {
b, err := bc.GetBlock(bc.GetHeaderHash(int(r.Index)))
if err != nil {
return err
}
return bc.VerifyWitness(b.NextConsensus, r, r.Witness, bc.contracts.Policy.GetMaxVerificationGas(bc.dao))
}

// VerifyTx verifies whether transaction is bonafide or not relative to the
// current blockchain state. Note that this verification is completely isolated
// from the main node's mempool.
Expand Down Expand Up @@ -1731,7 +1631,6 @@ func (bc *Blockchain) GetEnrollments() ([]state.Validator, error) {
// GetTestVM returns a VM and a Store setup for a test run of some sort of code.
func (bc *Blockchain) GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *vm.VM {
d := bc.dao.GetWrapped().(*dao.Simple)
d.MPT = nil
systemInterop := bc.newInteropContext(t, d, b, tx)
vm := systemInterop.SpawnVM()
vm.SetPriceGetter(systemInterop.GetPrice)
Expand Down
2 changes: 1 addition & 1 deletion pkg/core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func TestAddBlockStateRoot(t *testing.T) {
c.ProtocolConfiguration.StateRootInHeader = true
})

sr, err := bc.GetStateRoot(bc.BlockHeight())
sr, err := bc.GetStateModule().GetStateRoot(bc.BlockHeight())
require.NoError(t, err)

tx := newNEP17Transfer(bc.contracts.NEO.Hash, neoOwner, util.Uint160{}, 1)
Expand Down
4 changes: 1 addition & 3 deletions pkg/core/blockchainer/blockchainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ type Blockchainer interface {
GetConfig() config.ProtocolConfiguration
AddHeaders(...*block.Header) error
AddBlock(*block.Block) error
AddStateRoot(r *state.MPTRoot) error
CalculateClaimable(h util.Uint160, endHeight uint32) (*big.Int, error)
Close()
IsTxStillRelevant(t *transaction.Transaction, txpool *mempool.Pool, isPartialTx bool) bool
Expand Down Expand Up @@ -54,8 +53,7 @@ type Blockchainer interface {
GetValidators() ([]*keys.PublicKey, error)
GetStandByCommittee() keys.PublicKeys
GetStandByValidators() keys.PublicKeys
GetStateProof(root util.Uint256, key []byte) ([][]byte, error)
GetStateRoot(height uint32) (*state.MPTRootState, error)
GetStateModule() StateRoot
GetStorageItem(id int32, key []byte) state.StorageItem
GetStorageItems(id int32) (map[string]state.StorageItem, error)
GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *vm.VM
Expand Down
13 changes: 13 additions & 0 deletions pkg/core/blockchainer/state_root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package blockchainer

import (
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/util"
)

// StateRoot represents local state root module.
type StateRoot interface {
CurrentLocalStateRoot() util.Uint256
GetStateProof(root util.Uint256, key []byte) ([][]byte, error)
GetStateRoot(height uint32) (*state.MPTRoot, error)
}
Loading

0 comments on commit bf20db0

Please sign in to comment.