parent
8e3f2417f4
commit
49c995ec06
2 changed files with 257 additions and 13 deletions
|
@ -17,10 +17,12 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativeprices"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
|
@ -2003,3 +2005,227 @@ func setSigner(tx *transaction.Transaction, h util.Uint160) {
|
|||
Scopes: transaction.Global,
|
||||
}}
|
||||
}
|
||||
|
||||
func TestBlockchain_StartFromExistingDB(t *testing.T) {
|
||||
ps, path := newLevelDBForTestingWithPath(t, "")
|
||||
customConfig := func(c *config.Config) {
|
||||
c.ProtocolConfiguration.StateRootInHeader = true // Need for P2PStateExchangeExtensions check.
|
||||
}
|
||||
bc := initTestChain(t, ps, customConfig)
|
||||
go bc.Run()
|
||||
initBasicChain(t, bc)
|
||||
require.True(t, bc.BlockHeight() > 5, "ensure that basic chain is correctly initialised")
|
||||
|
||||
// Information for further tests.
|
||||
h := bc.BlockHeight()
|
||||
cryptoLibHash, err := bc.GetNativeContractScriptHash(nativenames.CryptoLib)
|
||||
require.NoError(t, err)
|
||||
cryptoLibState := bc.GetContractState(cryptoLibHash)
|
||||
require.NotNil(t, cryptoLibState)
|
||||
var (
|
||||
managementID = -1
|
||||
managementContractPrefix = 8
|
||||
)
|
||||
|
||||
bc.Close() // Ensure persist is done and persistent store is properly closed.
|
||||
|
||||
newPS := func(t *testing.T) storage.Store {
|
||||
ps, _ = newLevelDBForTestingWithPath(t, path)
|
||||
t.Cleanup(func() { require.NoError(t, ps.Close()) })
|
||||
return ps
|
||||
}
|
||||
t.Run("mismatch storage version", func(t *testing.T) {
|
||||
ps = newPS(t)
|
||||
cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption.
|
||||
d := dao.NewSimple(cache, bc.config.StateRootInHeader, bc.config.P2PStateExchangeExtensions)
|
||||
d.PutVersion(dao.Version{
|
||||
Value: "0.0.0",
|
||||
})
|
||||
_, err := d.Persist() // Persist to `cache` wrapper.
|
||||
require.NoError(t, err)
|
||||
_, err = initTestChainNoCheck(t, cache, customConfig)
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), "storage version mismatch"))
|
||||
})
|
||||
t.Run("mismatch StateRootInHeader", func(t *testing.T) {
|
||||
ps = newPS(t)
|
||||
_, err := initTestChainNoCheck(t, ps, func(c *config.Config) {
|
||||
customConfig(c)
|
||||
c.ProtocolConfiguration.StateRootInHeader = false
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), "StateRootInHeader setting mismatch"))
|
||||
})
|
||||
t.Run("mismatch P2PSigExtensions", func(t *testing.T) {
|
||||
ps = newPS(t)
|
||||
_, err := initTestChainNoCheck(t, ps, func(c *config.Config) {
|
||||
customConfig(c)
|
||||
c.ProtocolConfiguration.P2PSigExtensions = false
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), "P2PSigExtensions setting mismatch"))
|
||||
})
|
||||
t.Run("mismatch P2PStateExchangeExtensions", func(t *testing.T) {
|
||||
ps = newPS(t)
|
||||
_, err := initTestChainNoCheck(t, ps, func(c *config.Config) {
|
||||
customConfig(c)
|
||||
c.ProtocolConfiguration.StateRootInHeader = true
|
||||
c.ProtocolConfiguration.P2PStateExchangeExtensions = true
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), "P2PStateExchangeExtensions setting mismatch"))
|
||||
})
|
||||
t.Run("mismatch KeepOnlyLatestState", func(t *testing.T) {
|
||||
ps = newPS(t)
|
||||
_, err := initTestChainNoCheck(t, ps, func(c *config.Config) {
|
||||
customConfig(c)
|
||||
c.ProtocolConfiguration.KeepOnlyLatestState = true
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), "KeepOnlyLatestState setting mismatch"))
|
||||
})
|
||||
t.Run("corrupted headers", func(t *testing.T) {
|
||||
ps = newPS(t)
|
||||
|
||||
// Corrupt headers hashes batch.
|
||||
cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption.
|
||||
key := make([]byte, 5)
|
||||
key[0] = byte(storage.IXHeaderHashList)
|
||||
binary.BigEndian.PutUint32(key[1:], 1)
|
||||
cache.Put(key, []byte{1, 2, 3})
|
||||
|
||||
_, err := initTestChainNoCheck(t, cache, customConfig)
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), "failed to read batch of 2000"))
|
||||
})
|
||||
t.Run("corrupted current header height", func(t *testing.T) {
|
||||
ps = newPS(t)
|
||||
|
||||
// Remove current header.
|
||||
cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption.
|
||||
cache.Delete([]byte{byte(storage.SYSCurrentHeader)})
|
||||
|
||||
_, err := initTestChainNoCheck(t, cache, customConfig)
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), "failed to retrieve current header"))
|
||||
})
|
||||
t.Run("missing last batch of 2000 headers and missing last header", func(t *testing.T) {
|
||||
ps = newPS(t)
|
||||
|
||||
// Remove latest headers hashes batch and current header.
|
||||
cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption.
|
||||
cache.Delete([]byte{byte(storage.IXHeaderHashList)})
|
||||
currHeaderInfo, err := cache.Get([]byte{byte(storage.SYSCurrentHeader)})
|
||||
require.NoError(t, err)
|
||||
currHeaderHash, err := util.Uint256DecodeBytesLE(currHeaderInfo[:32])
|
||||
require.NoError(t, err)
|
||||
cache.Delete(append([]byte{byte(storage.DataExecutable)}, currHeaderHash.BytesBE()...))
|
||||
|
||||
_, err = initTestChainNoCheck(t, cache, customConfig)
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), "could not get header"))
|
||||
})
|
||||
t.Run("missing last block", func(t *testing.T) {
|
||||
ps = newPS(t)
|
||||
|
||||
// Remove current block from storage.
|
||||
cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption.
|
||||
cache.Delete([]byte{byte(storage.SYSCurrentBlock)})
|
||||
|
||||
_, err := initTestChainNoCheck(t, cache, customConfig)
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), "failed to retrieve current block height"))
|
||||
})
|
||||
t.Run("missing last stateroot", func(t *testing.T) {
|
||||
ps = newPS(t)
|
||||
|
||||
// Remove latest stateroot from storage.
|
||||
cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption.
|
||||
key := make([]byte, 5)
|
||||
key[0] = byte(storage.DataMPTAux)
|
||||
binary.BigEndian.PutUint32(key, h)
|
||||
cache.Delete(key)
|
||||
|
||||
_, err := initTestChainNoCheck(t, cache, customConfig)
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), "can't init MPT at height"))
|
||||
})
|
||||
t.Run("failed native Management initialisation", func(t *testing.T) {
|
||||
ps = newPS(t)
|
||||
|
||||
// Corrupt serialised CryptoLib state.
|
||||
cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption.
|
||||
key := make([]byte, 1+4+1+20)
|
||||
key[0] = byte(storage.STStorage)
|
||||
binary.LittleEndian.PutUint32(key[1:], uint32(managementID))
|
||||
key[5] = byte(managementContractPrefix)
|
||||
copy(key[6:], cryptoLibHash.BytesBE())
|
||||
cache.Put(key, []byte{1, 2, 3})
|
||||
|
||||
_, err := initTestChainNoCheck(t, cache, customConfig)
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), "can't init cache for Management native contract"))
|
||||
})
|
||||
t.Run("invalid native contract deactivation", func(t *testing.T) {
|
||||
ps = newPS(t)
|
||||
_, err := initTestChainNoCheck(t, ps, func(c *config.Config) {
|
||||
customConfig(c)
|
||||
c.ProtocolConfiguration.NativeUpdateHistories = map[string][]uint32{
|
||||
nativenames.Policy: {0},
|
||||
nativenames.Neo: {0},
|
||||
nativenames.Gas: {0},
|
||||
nativenames.Designation: {0},
|
||||
nativenames.StdLib: {0},
|
||||
nativenames.Management: {0},
|
||||
nativenames.Oracle: {0},
|
||||
nativenames.Ledger: {0},
|
||||
nativenames.Notary: {0},
|
||||
nativenames.CryptoLib: {h + 10},
|
||||
}
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native contract %s is already stored, but marked as inactive for height %d in config", nativenames.CryptoLib, h)))
|
||||
})
|
||||
t.Run("invalid native contract activation", func(t *testing.T) {
|
||||
ps = newPS(t)
|
||||
|
||||
// Remove CryptoLib from storage.
|
||||
cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption.
|
||||
key := make([]byte, 1+4+1+20)
|
||||
key[0] = byte(storage.STStorage)
|
||||
binary.LittleEndian.PutUint32(key[1:], uint32(managementID))
|
||||
key[5] = byte(managementContractPrefix)
|
||||
copy(key[6:], cryptoLibHash.BytesBE())
|
||||
cache.Delete(key)
|
||||
|
||||
_, err := initTestChainNoCheck(t, cache, customConfig)
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native contract %s is not stored, but should be active at height %d according to config", nativenames.CryptoLib, h)))
|
||||
})
|
||||
t.Run("stored and autogenerated native contract's states mismatch", func(t *testing.T) {
|
||||
ps = newPS(t)
|
||||
|
||||
// Change stored CryptoLib state.
|
||||
cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption.
|
||||
key := make([]byte, 1+4+1+20)
|
||||
key[0] = byte(storage.STStorage)
|
||||
binary.LittleEndian.PutUint32(key[1:], uint32(managementID))
|
||||
key[5] = byte(managementContractPrefix)
|
||||
copy(key[6:], cryptoLibHash.BytesBE())
|
||||
cs := *cryptoLibState
|
||||
cs.ID = -123
|
||||
csBytes, err := stackitem.SerializeConvertible(&cs)
|
||||
require.NoError(t, err)
|
||||
cache.Put(key, csBytes)
|
||||
|
||||
_, err = initTestChainNoCheck(t, cache, customConfig)
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native %s: version mismatch (stored contract state differs from autogenerated one)", nativenames.CryptoLib)))
|
||||
})
|
||||
|
||||
t.Run("good", func(t *testing.T) {
|
||||
ps = newPS(t)
|
||||
_, err := initTestChainNoCheck(t, ps, customConfig)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -68,24 +68,44 @@ func newTestChainWithCustomCfgAndStore(t testing.TB, st storage.Store, f func(*c
|
|||
}
|
||||
|
||||
func newLevelDBForTesting(t testing.TB) storage.Store {
|
||||
ldbDir := t.TempDir()
|
||||
dbOptions := storage.LevelDBOptions{
|
||||
DataDirectoryPath: ldbDir,
|
||||
}
|
||||
newLevelStore, err := storage.NewLevelDBStore(dbOptions)
|
||||
require.Nil(t, err, "NewLevelDBStore error")
|
||||
newLevelStore, _ := newLevelDBForTestingWithPath(t, "")
|
||||
return newLevelStore
|
||||
}
|
||||
|
||||
func newLevelDBForTestingWithPath(t testing.TB, dbPath string) (storage.Store, string) {
|
||||
if dbPath == "" {
|
||||
dbPath = t.TempDir()
|
||||
}
|
||||
dbOptions := storage.LevelDBOptions{
|
||||
DataDirectoryPath: dbPath,
|
||||
}
|
||||
newLevelStore, err := storage.NewLevelDBStore(dbOptions)
|
||||
require.Nil(t, err, "NewLevelDBStore error")
|
||||
return newLevelStore, dbPath
|
||||
}
|
||||
|
||||
func newBoltStoreForTesting(t testing.TB) storage.Store {
|
||||
d := t.TempDir()
|
||||
testFileName := filepath.Join(d, "test_bolt_db")
|
||||
boltDBStore, err := storage.NewBoltDBStore(storage.BoltDBOptions{FilePath: testFileName})
|
||||
require.NoError(t, err)
|
||||
boltDBStore, _ := newBoltStoreForTestingWithPath(t, "")
|
||||
return boltDBStore
|
||||
}
|
||||
|
||||
func newBoltStoreForTestingWithPath(t testing.TB, dbPath string) (storage.Store, string) {
|
||||
if dbPath == "" {
|
||||
d := t.TempDir()
|
||||
dbPath = filepath.Join(d, "test_bolt_db")
|
||||
}
|
||||
boltDBStore, err := storage.NewBoltDBStore(storage.BoltDBOptions{FilePath: dbPath})
|
||||
require.NoError(t, err)
|
||||
return boltDBStore, dbPath
|
||||
}
|
||||
|
||||
func initTestChain(t testing.TB, st storage.Store, f func(*config.Config)) *Blockchain {
|
||||
chain, err := initTestChainNoCheck(t, st, f)
|
||||
require.NoError(t, err)
|
||||
return chain
|
||||
}
|
||||
|
||||
func initTestChainNoCheck(t testing.TB, st storage.Store, f func(*config.Config)) (*Blockchain, error) {
|
||||
unitTestNetCfg, err := config.Load("../../config", testchain.Network())
|
||||
require.NoError(t, err)
|
||||
if f != nil {
|
||||
|
@ -98,9 +118,7 @@ func initTestChain(t testing.TB, st storage.Store, f func(*config.Config)) *Bloc
|
|||
if _, ok := t.(*testing.B); ok {
|
||||
log = zap.NewNop()
|
||||
}
|
||||
chain, err := NewBlockchain(st, unitTestNetCfg.ProtocolConfiguration, log)
|
||||
require.NoError(t, err)
|
||||
return chain
|
||||
return NewBlockchain(st, unitTestNetCfg.ProtocolConfiguration, log)
|
||||
}
|
||||
|
||||
func (bc *Blockchain) newBlock(txs ...*transaction.Transaction) *block.Block {
|
||||
|
|
Loading…
Reference in a new issue