diff --git a/cli/query/query_test.go b/cli/query/query_test.go index c85d282e0..0f7bbd4e6 100644 --- a/cli/query/query_test.go +++ b/cli/query/query_test.go @@ -63,7 +63,7 @@ func TestQueryTx(t *testing.T) { _, height, err := e.Chain.GetTransaction(txHash) require.NoError(t, err) - e.CheckNextLine(t, `BlockHash:\s+`+e.Chain.GetHeaderHash(int(height)).StringLE()) + e.CheckNextLine(t, `BlockHash:\s+`+e.Chain.GetHeaderHash(height).StringLE()) e.CheckNextLine(t, `Success:\s+true`) e.CheckEOF(t) @@ -117,7 +117,7 @@ func compareQueryTxVerbose(t *testing.T, e *testcli.Executor, tx *transaction.Tr e.CheckNextLine(t, `OnChain:\s+true`) _, height, err := e.Chain.GetTransaction(tx.Hash()) require.NoError(t, err) - e.CheckNextLine(t, `BlockHash:\s+`+e.Chain.GetHeaderHash(int(height)).StringLE()) + e.CheckNextLine(t, `BlockHash:\s+`+e.Chain.GetHeaderHash(height).StringLE()) res, _ := e.Chain.GetAppExecResults(tx.Hash(), trigger.Application) e.CheckNextLine(t, fmt.Sprintf(`Success:\s+%t`, res[0].Execution.VMState == vmstate.Halt)) diff --git a/internal/fakechain/fakechain.go b/internal/fakechain/fakechain.go index 34c1f9b6c..4ba989fdc 100644 --- a/internal/fakechain/fakechain.go +++ b/internal/fakechain/fakechain.go @@ -2,7 +2,6 @@ package fakechain import ( "errors" - "math" "math/big" "sync/atomic" @@ -236,11 +235,8 @@ func (chain *FakeChain) GetNativeContractScriptHash(name string) (util.Uint160, } // GetHeaderHash implements the Blockchainer interface. -func (chain *FakeChain) GetHeaderHash(n int) util.Uint256 { - if n < 0 || n > math.MaxUint32 { - return util.Uint256{} - } - return chain.hdrHashes[uint32(n)] +func (chain *FakeChain) GetHeaderHash(n uint32) util.Uint256 { + return chain.hdrHashes[n] } // GetHeader implements the Blockchainer interface. diff --git a/internal/testchain/address.go b/internal/testchain/address.go index d116214fc..3db905638 100644 --- a/internal/testchain/address.go +++ b/internal/testchain/address.go @@ -158,7 +158,7 @@ func SignCommittee(h hash.Hashable) []byte { func NewBlock(t *testing.T, bc Ledger, offset uint32, primary uint32, txs ...*transaction.Transaction) *block.Block { witness := transaction.Witness{VerificationScript: MultisigVerificationScript()} height := bc.BlockHeight() - h := bc.GetHeaderHash(int(height)) + h := bc.GetHeaderHash(height) hdr, err := bc.GetHeader(h) require.NoError(t, err) b := &block.Block{ diff --git a/internal/testchain/transaction.go b/internal/testchain/transaction.go index 2e05a804c..630902ac8 100644 --- a/internal/testchain/transaction.go +++ b/internal/testchain/transaction.go @@ -27,7 +27,7 @@ type Ledger interface { FeePerByte() int64 GetBaseExecFee() int64 GetHeader(hash util.Uint256) (*block.Header, error) - GetHeaderHash(int) util.Uint256 + GetHeaderHash(uint32) util.Uint256 HeaderHeight() uint32 ManagementContractHash() util.Uint160 } diff --git a/pkg/consensus/consensus_test.go b/pkg/consensus/consensus_test.go index fcca5b2f5..c3f3c6098 100644 --- a/pkg/consensus/consensus_test.go +++ b/pkg/consensus/consensus_test.go @@ -119,7 +119,7 @@ func TestService_NextConsensus(t *testing.T) { require.NoError(t, err) checkNextConsensus := func(t *testing.T, bc *core.Blockchain, height uint32, h util.Uint160) { - hdrHash := bc.GetHeaderHash(int(height)) + hdrHash := bc.GetHeaderHash(height) hdr, err := bc.GetHeader(hdrHash) require.NoError(t, err) require.Equal(t, h, hdr.NextConsensus) diff --git a/pkg/core/bench_test.go b/pkg/core/bench_test.go index 2885487b7..f7d25e67d 100644 --- a/pkg/core/bench_test.go +++ b/pkg/core/bench_test.go @@ -109,10 +109,10 @@ func benchmarkForEachNEP17Transfer(t *testing.B, ps storage.Store, startFromBloc e.CheckHalt(t, tx.Hash()) } - newestB, err := bc.GetBlock(bc.GetHeaderHash(int(bc.BlockHeight()) - startFromBlock + 1)) + newestB, err := bc.GetBlock(bc.GetHeaderHash(bc.BlockHeight() - uint32(startFromBlock) + 1)) require.NoError(t, err) newestTimestamp := newestB.Timestamp - oldestB, err := bc.GetBlock(bc.GetHeaderHash(int(newestB.Index) - nBlocksToTake)) + oldestB, err := bc.GetBlock(bc.GetHeaderHash(newestB.Index - uint32(nBlocksToTake))) require.NoError(t, err) oldestTimestamp := oldestB.Timestamp diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 59b0404ef..490e6c699 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -45,8 +45,7 @@ import ( // Tuning parameters. const ( - headerBatchCount = 2000 - version = "0.2.6" + version = "0.2.6" defaultInitialGAS = 52000000_00000000 defaultGCPeriod = 10000 @@ -115,6 +114,8 @@ var ( // the state of the ledger that can be accessed in various ways and changed by // adding new blocks or headers. type Blockchain struct { + HeaderHashes + config config.ProtocolConfiguration // The only way chain state changes is by adding blocks, so we can't @@ -151,13 +152,6 @@ type Blockchain struct { // Current persisted block count. persistedHeight uint32 - // Number of headers stored in the chain file. - storedHeaderCount uint32 - - // Header hashes list with associated lock. - headerHashesLock sync.RWMutex - headerHashes []util.Uint256 - // Stop synchronization mechanisms. stopCh chan struct{} runToExitCh chan struct{} @@ -380,8 +374,7 @@ func (bc *Blockchain) init() error { if err != nil { return err } - bc.headerHashes = []util.Uint256{genesisBlock.Hash()} - bc.dao.PutCurrentHeader(genesisBlock.Hash(), genesisBlock.Index) + bc.HeaderHashes.initGenesis(bc.dao, genesisBlock.Hash()) if err := bc.stateRoot.Init(0); err != nil { return fmt.Errorf("can't init MPT: %w", err) } @@ -414,53 +407,11 @@ func (bc *Blockchain) init() error { // and the genesis block as first block. bc.log.Info("restoring blockchain", zap.String("version", version)) - bc.headerHashes, err = bc.dao.GetHeaderHashes() + err = bc.HeaderHashes.init(bc.dao) if err != nil { return err } - bc.storedHeaderCount = uint32(len(bc.headerHashes)) - - currHeaderHeight, currHeaderHash, err := bc.dao.GetCurrentHeaderHeight() - if err != nil { - return fmt.Errorf("failed to retrieve current header info: %w", err) - } - if bc.storedHeaderCount == 0 && currHeaderHeight == 0 { - bc.headerHashes = append(bc.headerHashes, currHeaderHash) - } - - // There is a high chance that the Node is stopped before the next - // batch of 2000 headers was stored. Via the currentHeaders stored we can sync - // that with stored blocks. - if currHeaderHeight >= bc.storedHeaderCount { - hash := currHeaderHash - var targetHash util.Uint256 - if len(bc.headerHashes) > 0 { - targetHash = bc.headerHashes[len(bc.headerHashes)-1] - } else { - genesisBlock, err := CreateGenesisBlock(bc.config) - if err != nil { - return err - } - targetHash = genesisBlock.Hash() - bc.headerHashes = append(bc.headerHashes, targetHash) - } - headers := make([]*block.Header, 0) - - for hash != targetHash { - header, err := bc.GetHeader(hash) - if err != nil { - return fmt.Errorf("could not get header %s: %w", hash, err) - } - headers = append(headers, header) - hash = header.PrevHash - } - headerSliceReverse(headers) - for _, h := range headers { - bc.headerHashes = append(bc.headerHashes, h.Hash()) - } - } - // Check whether StateChangeState stage is in the storage and continue interrupted state jump / state reset if so. stateChStage, err := bc.dao.Store.Get([]byte{byte(storage.SYSStateChangeStage)}) if err == nil { @@ -551,8 +502,8 @@ func (bc *Blockchain) jumpToState(p uint32) error { // jump stage. All the data needed for the jump must be in the DB, otherwise an // error is returned. It is not protected by mutex. func (bc *Blockchain) jumpToStateInternal(p uint32, stage stateChangeStage) error { - if p+1 >= uint32(len(bc.headerHashes)) { - return fmt.Errorf("invalid state sync point %d: headerHeignt is %d", p, len(bc.headerHashes)) + if p >= bc.HeaderHeight() { + return fmt.Errorf("invalid state sync point %d: headerHeignt is %d", p, bc.HeaderHeight()) } bc.log.Info("jumping to state sync point", zap.Uint32("state sync point", p)) @@ -587,7 +538,7 @@ func (bc *Blockchain) jumpToStateInternal(p uint32, stage stateChangeStage) erro // After current state is updated, we need to remove outdated state-related data if so. // The only outdated data we might have is genesis-related data, so check it. if p-bc.config.MaxTraceableBlocks > 0 { - err := cache.DeleteBlock(bc.headerHashes[0]) + err := cache.DeleteBlock(bc.GetHeaderHash(0)) if err != nil { return fmt.Errorf("failed to remove outdated state data for the genesis block: %w", err) } @@ -600,7 +551,7 @@ func (bc *Blockchain) jumpToStateInternal(p uint32, stage stateChangeStage) erro } } // Update SYS-prefixed info. - block, err := bc.dao.GetBlock(bc.headerHashes[p]) + block, err := bc.dao.GetBlock(bc.GetHeaderHash(p)) if err != nil { return fmt.Errorf("failed to get current block: %w", err) } @@ -616,7 +567,7 @@ func (bc *Blockchain) jumpToStateInternal(p uint32, stage stateChangeStage) erro default: return fmt.Errorf("unknown state jump stage: %d", stage) } - block, err := bc.dao.GetBlock(bc.headerHashes[p+1]) + block, err := bc.dao.GetBlock(bc.GetHeaderHash(p + 1)) if err != nil { return fmt.Errorf("failed to get block to init MPT: %w", err) } @@ -637,10 +588,12 @@ func (bc *Blockchain) jumpToStateInternal(p uint32, stage stateChangeStage) erro // resetRAMState resets in-memory cached info. func (bc *Blockchain) resetRAMState(height uint32, resetHeaders bool) error { if resetHeaders { - bc.headerHashes = bc.headerHashes[:height+1] - bc.storedHeaderCount = height + 1 + err := bc.HeaderHashes.init(bc.dao) + if err != nil { + return err + } } - block, err := bc.dao.GetBlock(bc.headerHashes[height]) + block, err := bc.dao.GetBlock(bc.GetHeaderHash(height)) if err != nil { return fmt.Errorf("failed to get current block: %w", err) } @@ -697,7 +650,7 @@ func (bc *Blockchain) resetStateInternal(height uint32, stage stateChangeStage) } // Retrieve necessary state before the DB modification. - b, err := bc.GetBlock(bc.headerHashes[height]) + b, err := bc.GetBlock(bc.GetHeaderHash(height)) if err != nil { return fmt.Errorf("failed to retrieve block %d: %w", height, err) } @@ -733,7 +686,7 @@ func (bc *Blockchain) resetStateInternal(height uint32, stage stateChangeStage) blocksCnt, batchCnt, keysCnt int ) for i := height + 1; i <= currHeight; i++ { - err := cache.DeleteBlock(bc.GetHeaderHash(int(i))) + err := cache.DeleteBlock(bc.GetHeaderHash(i)) if err != nil { return fmt.Errorf("error while removing block %d: %w", i, err) } @@ -861,7 +814,7 @@ func (bc *Blockchain) resetStateInternal(height uint32, stage stateChangeStage) // Reset SYS-prefixed and IX-prefixed information. bc.log.Info("trying to reset headers information") for i := height + 1; i <= hHeight; i++ { - cache.PurgeHeader(bc.GetHeaderHash(int(i))) + cache.PurgeHeader(bc.GetHeaderHash(i)) } cache.DeleteHeaderHashes(height+1, headerBatchCount) cache.StoreAsCurrentBlock(b) @@ -1186,7 +1139,7 @@ func appendTokenTransferInfo(transferData *state.TokenTransferInfo, func (bc *Blockchain) removeOldTransfers(index uint32) time.Duration { bc.log.Info("starting transfer data garbage collection", zap.Uint32("index", index)) start := time.Now() - h, err := bc.GetHeader(bc.GetHeaderHash(int(index))) + h, err := bc.GetHeader(bc.GetHeaderHash(index)) if err != nil { dur := time.Since(start) bc.log.Error("failed to find block header for transfer GC", zap.Duration("time", dur), zap.Error(err)) @@ -1418,7 +1371,6 @@ func (bc *Blockchain) AddHeaders(headers ...*block.Header) error { func (bc *Blockchain) addHeaders(verify bool, headers ...*block.Header) error { var ( start = time.Now() - batch = bc.dao.GetPrivate() err error ) @@ -1448,44 +1400,14 @@ func (bc *Blockchain) addHeaders(verify bool, headers ...*block.Header) error { lastHeader = h } } - - bc.headerHashesLock.Lock() - defer bc.headerHashesLock.Unlock() - oldlen := len(bc.headerHashes) - var lastHeader *block.Header - for _, h := range headers { - if int(h.Index) != len(bc.headerHashes) { - continue - } - err = batch.StoreHeader(h) - if err != nil { - return err - } - bc.headerHashes = append(bc.headerHashes, h.Hash()) - lastHeader = h - } - - if oldlen != len(bc.headerHashes) { - for int(lastHeader.Index)-headerBatchCount >= int(bc.storedHeaderCount) { - err = batch.StoreHeaderHashes(bc.headerHashes[bc.storedHeaderCount:bc.storedHeaderCount+headerBatchCount], - bc.storedHeaderCount) - if err != nil { - return err - } - bc.storedHeaderCount += headerBatchCount - } - - batch.PutCurrentHeader(lastHeader.Hash(), lastHeader.Index) - updateHeaderHeightMetric(len(bc.headerHashes) - 1) - if _, err = batch.Persist(); err != nil { - return err - } + res := bc.HeaderHashes.addHeaders(headers...) + if res == nil { bc.log.Debug("done processing headers", - zap.Int("headerIndex", len(bc.headerHashes)-1), + zap.Uint32("headerIndex", bc.HeaderHeight()), zap.Uint32("blockHeight", bc.BlockHeight()), zap.Duration("took", time.Since(start))) } - return nil + return res } // GetStateRoot returns state root for the given height. @@ -1540,7 +1462,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error stop = start + 1 } for index := start; index < stop; index++ { - err := kvcache.DeleteBlock(bc.headerHashes[index]) + err := kvcache.DeleteBlock(bc.GetHeaderHash(index)) if err != nil { bc.log.Warn("error while removing old block", zap.Uint32("index", index), @@ -1662,7 +1584,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error return fmt.Errorf("error while trying to apply MPT changes: %w", err) } if bc.config.StateRootInHeader && bc.HeaderHeight() > sr.Index { - h, err := bc.GetHeader(bc.GetHeaderHash(int(sr.Index) + 1)) + h, err := bc.GetHeader(bc.GetHeaderHash(sr.Index + 1)) if err != nil { err = fmt.Errorf("failed to get next header: %w", err) } else if h.PrevStateRoot != sr.Root { @@ -2163,15 +2085,9 @@ func (bc *Blockchain) HasTransaction(hash util.Uint256) bool { // HasBlock returns true if the blockchain contains the given // block hash. func (bc *Blockchain) HasBlock(hash util.Uint256) bool { - var height = bc.BlockHeight() - bc.headerHashesLock.RLock() - for i := int(height); i >= int(height)-4 && i >= 0; i-- { - if hash.Equals(bc.headerHashes[i]) { - bc.headerHashesLock.RUnlock() - return true - } + if bc.HeaderHashes.haveRecentHash(hash, bc.BlockHeight()) { + return true } - bc.headerHashesLock.RUnlock() if header, err := bc.GetHeader(hash); err == nil { return header.Index <= bc.BlockHeight() @@ -2186,28 +2102,7 @@ func (bc *Blockchain) CurrentBlockHash() util.Uint256 { tb := topBlock.(*block.Block) return tb.Hash() } - return bc.GetHeaderHash(int(bc.BlockHeight())) -} - -// CurrentHeaderHash returns the hash of the latest known header. -func (bc *Blockchain) CurrentHeaderHash() util.Uint256 { - bc.headerHashesLock.RLock() - hash := bc.headerHashes[len(bc.headerHashes)-1] - bc.headerHashesLock.RUnlock() - return hash -} - -// GetHeaderHash returns hash of the header/block with specified index, if -// Blockchain doesn't have a hash for this height, zero Uint256 value is returned. -func (bc *Blockchain) GetHeaderHash(i int) util.Uint256 { - bc.headerHashesLock.RLock() - defer bc.headerHashesLock.RUnlock() - - hashesLen := len(bc.headerHashes) - if hashesLen <= i { - return util.Uint256{} - } - return bc.headerHashes[i] + return bc.GetHeaderHash(bc.BlockHeight()) } // BlockHeight returns the height/index of the highest block. @@ -2215,14 +2110,6 @@ func (bc *Blockchain) BlockHeight() uint32 { return atomic.LoadUint32(&bc.blockHeight) } -// HeaderHeight returns the index/height of the highest header. -func (bc *Blockchain) HeaderHeight() uint32 { - bc.headerHashesLock.RLock() - n := len(bc.headerHashes) - bc.headerHashesLock.RUnlock() - return uint32(n - 1) -} - // GetContractState returns contract by its script hash. func (bc *Blockchain) GetContractState(hash util.Uint160) *state.Contract { contract, err := bc.contracts.Management.GetContract(bc.dao, hash) @@ -2759,7 +2646,7 @@ func (bc *Blockchain) GetTestHistoricVM(t trigger.Type, tx *transaction.Transact func (bc *Blockchain) getFakeNextBlock(nextBlockHeight uint32) (*block.Block, error) { b := block.New(bc.config.StateRootInHeader) b.Index = nextBlockHeight - hdr, err := bc.GetHeader(bc.GetHeaderHash(int(nextBlockHeight - 1))) + hdr, err := bc.GetHeader(bc.GetHeaderHash(nextBlockHeight - 1)) if err != nil { return nil, err } diff --git a/pkg/core/blockchain_core_test.go b/pkg/core/blockchain_core_test.go index 500b526f8..529d12f49 100644 --- a/pkg/core/blockchain_core_test.go +++ b/pkg/core/blockchain_core_test.go @@ -225,7 +225,7 @@ func TestBlockchain_InitWithIncompleteStateJump(t *testing.T) { t.Run("invalid state sync point", func(t *testing.T) { bcSpout.dao.Store.Put(bPrefix, []byte{byte(stateJumpStarted)}) point := make([]byte, 4) - binary.LittleEndian.PutUint32(point, uint32(len(bcSpout.headerHashes))) + binary.LittleEndian.PutUint32(point, bcSpout.lastHeaderIndex()+1) bcSpout.dao.Store.Put([]byte{byte(storage.SYSStateSyncPoint)}, point) checkNewBlockchainErr(t, boltCfg, bcSpout.dao.Store, "invalid state sync point") }) @@ -304,7 +304,7 @@ func TestChainWithVolatileNumOfValidators(t *testing.T) { }, } curWit = nextWit - b.PrevHash = bc.GetHeaderHash(i - 1) + b.PrevHash = bc.GetHeaderHash(uint32(i) - 1) b.Timestamp = uint64(time.Now().UTC().Unix())*1000 + uint64(i) b.Index = uint32(i) b.RebuildMerkleRoot() diff --git a/pkg/core/blockchain_neotest_test.go b/pkg/core/blockchain_neotest_test.go index 3c325dc4a..d7b8cbfcf 100644 --- a/pkg/core/blockchain_neotest_test.go +++ b/pkg/core/blockchain_neotest_test.go @@ -146,14 +146,15 @@ func TestBlockchain_StartFromExistingDB(t *testing.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}) + // Make the chain think we're at 2000+ which will trigger page 0 read. + buf := io.NewBufBinWriter() + buf.WriteBytes(util.Uint256{}.BytesLE()) + buf.WriteU32LE(2000) + cache.Put([]byte{byte(storage.SYSCurrentHeader)}, buf.Bytes()) _, _, _, err := chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, cache) require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "failed to read batch of 2000"), err) + require.True(t, strings.Contains(err.Error(), "failed to retrieve header hash page"), err) }) t.Run("corrupted current header height", func(t *testing.T) { ps = newPS(t) @@ -1970,12 +1971,12 @@ func TestBlockchain_ResetState(t *testing.T) { neoH := e.NativeHash(t, nativenames.Neo) gasID := e.NativeID(t, nativenames.Gas) neoID := e.NativeID(t, nativenames.Neo) - resetBlockHash := bc.GetHeaderHash(int(resetBlockIndex)) + resetBlockHash := bc.GetHeaderHash(resetBlockIndex) resetBlockHeader, err := bc.GetHeader(resetBlockHash) require.NoError(t, err) topBlockHeight := bc.BlockHeight() - topBH := bc.GetHeaderHash(int(bc.BlockHeight())) - staleBH := bc.GetHeaderHash(int(resetBlockIndex + 1)) + topBH := bc.GetHeaderHash(bc.BlockHeight()) + staleBH := bc.GetHeaderHash(resetBlockIndex + 1) staleB, err := bc.GetBlock(staleBH) require.NoError(t, err) staleTx := staleB.Transactions[0] @@ -2043,7 +2044,7 @@ func TestBlockchain_ResetState(t *testing.T) { require.Equal(t, uint32(0), bc.GetStateModule().CurrentValidatedHeight()) // Try to get the latest block\header. - bh := bc.GetHeaderHash(int(resetBlockIndex)) + bh := bc.GetHeaderHash(resetBlockIndex) require.Equal(t, resetBlockHash, bh) h, err := bc.GetHeader(bh) require.NoError(t, err) @@ -2054,7 +2055,7 @@ func TestBlockchain_ResetState(t *testing.T) { // Check that stale blocks/headers/txs/aers/sr are not reachable. for i := resetBlockIndex + 1; i <= topBlockHeight; i++ { - hHash := bc.GetHeaderHash(int(i)) + hHash := bc.GetHeaderHash(i) require.Equal(t, util.Uint256{}, hHash) _, err = bc.GetStateRoot(i) require.Error(t, err) diff --git a/pkg/core/chaindump/dump.go b/pkg/core/chaindump/dump.go index d2cf45469..802395591 100644 --- a/pkg/core/chaindump/dump.go +++ b/pkg/core/chaindump/dump.go @@ -14,14 +14,14 @@ type DumperRestorer interface { AddBlock(block *block.Block) error GetBlock(hash util.Uint256) (*block.Block, error) GetConfig() config.ProtocolConfiguration - GetHeaderHash(int) util.Uint256 + GetHeaderHash(uint32) util.Uint256 } // Dump writes count blocks from start to the provided writer. // Note: header needs to be written separately by a client. func Dump(bc DumperRestorer, w *io.BinWriter, start, count uint32) error { for i := start; i < start+count; i++ { - bh := bc.GetHeaderHash(int(i)) + bh := bc.GetHeaderHash(i) b, err := bc.GetBlock(bh) if err != nil { return err diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index f802dab52..8fb33efaa 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -1,7 +1,6 @@ package dao import ( - "bytes" "context" "encoding/binary" "errors" @@ -582,25 +581,23 @@ func (dao *Simple) GetStateSyncCurrentBlockHeight() (uint32, error) { return binary.LittleEndian.Uint32(b), nil } -// GetHeaderHashes returns a sorted list of header hashes retrieved from +// GetHeaderHashes returns a page of header hashes retrieved from // the given underlying store. -func (dao *Simple) GetHeaderHashes() ([]util.Uint256, error) { - var hashes = make([]util.Uint256, 0) +func (dao *Simple) GetHeaderHashes(height uint32) ([]util.Uint256, error) { + var hashes []util.Uint256 - var seekErr error - dao.Store.Seek(storage.SeekRange{ - Prefix: dao.mkKeyPrefix(storage.IXHeaderHashList), - }, func(k, v []byte) bool { - newHashes, err := read2000Uint256Hashes(v) - if err != nil { - seekErr = fmt.Errorf("failed to read batch of 2000 header hashes: %w", err) - return false - } - hashes = append(hashes, newHashes...) - return true - }) + key := dao.mkHeaderHashKey(height) + b, err := dao.Store.Get(key) + if err != nil { + return nil, err + } - return hashes, seekErr + br := io.NewBinReaderFromBuf(b) + br.ReadArray(&hashes) + if br.Err != nil { + return nil, br.Err + } + return hashes, nil } // DeleteHeaderHashes removes batches of header hashes starting from the one that @@ -683,19 +680,6 @@ func (dao *Simple) PutStateSyncCurrentBlockHeight(h uint32) { dao.Store.Put(dao.mkKeyPrefix(storage.SYSStateSyncCurrentBlockHeight), buf.Bytes()) } -// read2000Uint256Hashes attempts to read 2000 Uint256 hashes from -// the given byte array. -func read2000Uint256Hashes(b []byte) ([]util.Uint256, error) { - r := bytes.NewReader(b) - br := io.NewBinReaderFromIO(r) - hashes := make([]util.Uint256, 0) - br.ReadArray(&hashes) - if br.Err != nil { - return nil, br.Err - } - return hashes, nil -} - func (dao *Simple) mkHeaderHashKey(h uint32) []byte { b := dao.getKeyBuf(1 + 4) b[0] = byte(storage.IXHeaderHashList) diff --git a/pkg/core/headerhashes.go b/pkg/core/headerhashes.go new file mode 100644 index 000000000..4d9ce042a --- /dev/null +++ b/pkg/core/headerhashes.go @@ -0,0 +1,211 @@ +package core + +import ( + "fmt" + "sync" + + lru "github.com/hashicorp/golang-lru" + "github.com/nspcc-dev/neo-go/pkg/core/block" + "github.com/nspcc-dev/neo-go/pkg/core/dao" + "github.com/nspcc-dev/neo-go/pkg/util" +) + +const ( + headerBatchCount = 2000 + pagesCache = 8 +) + +// HeaderHashes is a header hash manager part of the Blockchain. It can't be used +// without Blockchain. +type HeaderHashes struct { + // Backing storage. + dao *dao.Simple + + // Lock for all internal state fields. + lock sync.RWMutex + + // The latest header hashes (storedHeaderCount+). + latest []util.Uint256 + + // Previously completed page of header hashes (pre-storedHeaderCount). + previous []util.Uint256 + + // Number of headers stored in the chain file. + storedHeaderCount uint32 + + // Cache for accessed pages of header hashes. + cache *lru.Cache +} + +func (h *HeaderHashes) initGenesis(dao *dao.Simple, hash util.Uint256) { + h.dao = dao + h.cache, _ = lru.New(pagesCache) // Never errors for positive size. + h.previous = make([]util.Uint256, headerBatchCount) + h.latest = make([]util.Uint256, 0, headerBatchCount) + h.latest = append(h.latest, hash) + dao.PutCurrentHeader(hash, 0) +} + +func (h *HeaderHashes) init(dao *dao.Simple) error { + h.dao = dao + h.cache, _ = lru.New(pagesCache) // Never errors for positive size. + + currHeaderHeight, currHeaderHash, err := h.dao.GetCurrentHeaderHeight() + if err != nil { + return fmt.Errorf("failed to retrieve current header info: %w", err) + } + h.storedHeaderCount = ((currHeaderHeight + 1) / headerBatchCount) * headerBatchCount + + if h.storedHeaderCount >= headerBatchCount { + h.previous, err = h.dao.GetHeaderHashes(h.storedHeaderCount - headerBatchCount) + if err != nil { + return fmt.Errorf("failed to retrieve header hash page %d: %w", h.storedHeaderCount-headerBatchCount, err) + } + } else { + h.previous = make([]util.Uint256, headerBatchCount) + } + h.latest = make([]util.Uint256, 0, headerBatchCount) + + // There is a high chance that the Node is stopped before the next + // batch of 2000 headers was stored. Via the currentHeaders stored we can sync + // that with stored blocks. + if currHeaderHeight >= h.storedHeaderCount { + hash := currHeaderHash + var targetHash util.Uint256 + if h.storedHeaderCount >= headerBatchCount { + targetHash = h.previous[len(h.previous)-1] + } + headers := make([]util.Uint256, 0, headerBatchCount) + + for hash != targetHash { + blk, err := h.dao.GetBlock(hash) + if err != nil { + return fmt.Errorf("could not get header %s: %w", hash, err) + } + headers = append(headers, blk.Hash()) + hash = blk.PrevHash + } + hashSliceReverse(headers) + h.latest = append(h.latest, headers...) + } + return nil +} + +func (h *HeaderHashes) lastHeaderIndex() uint32 { + return h.storedHeaderCount + uint32(len(h.latest)) - 1 +} + +// HeaderHeight returns the index/height of the highest header. +func (h *HeaderHashes) HeaderHeight() uint32 { + h.lock.RLock() + n := h.lastHeaderIndex() + h.lock.RUnlock() + return n +} + +func (h *HeaderHashes) addHeaders(headers ...*block.Header) error { + var ( + batch = h.dao.GetPrivate() + lastHeader *block.Header + err error + ) + + h.lock.Lock() + defer h.lock.Unlock() + + for _, head := range headers { + if head.Index != h.lastHeaderIndex()+1 { + continue + } + err = batch.StoreHeader(head) + if err != nil { + return err + } + lastHeader = head + h.latest = append(h.latest, head.Hash()) + if len(h.latest) == headerBatchCount { + err = batch.StoreHeaderHashes(h.latest, h.storedHeaderCount) + if err != nil { + return err + } + copy(h.previous, h.latest) + h.latest = h.latest[:0] + h.storedHeaderCount += headerBatchCount + } + } + if lastHeader != nil { + batch.PutCurrentHeader(lastHeader.Hash(), lastHeader.Index) + updateHeaderHeightMetric(lastHeader.Index) + if _, err = batch.Persist(); err != nil { + return err + } + } + return nil +} + +// CurrentHeaderHash returns the hash of the latest known header. +func (h *HeaderHashes) CurrentHeaderHash() util.Uint256 { + var hash util.Uint256 + + h.lock.RLock() + if len(h.latest) > 0 { + hash = h.latest[len(h.latest)-1] + } else { + hash = h.previous[len(h.previous)-1] + } + h.lock.RUnlock() + return hash +} + +// GetHeaderHash returns hash of the header/block with specified index, if +// HeaderHashes doesn't have a hash for this height, zero Uint256 value is returned. +func (h *HeaderHashes) GetHeaderHash(i uint32) util.Uint256 { + h.lock.RLock() + res, ok := h.getLocalHeaderHash(i) + h.lock.RUnlock() + if ok { + return res + } + // If it's not in the latest/previous, then it's in the cache or DB, those + // need no additional locks. + page := (i / headerBatchCount) * headerBatchCount + cache, ok := h.cache.Get(page) + if ok { + hashes := cache.([]util.Uint256) + return hashes[i-page] + } + hashes, err := h.dao.GetHeaderHashes(page) + if err != nil { + return util.Uint256{} + } + _ = h.cache.Add(page, hashes) + return hashes[i-page] +} + +// getLocalHeaderHash looks for the index in the latest and previous caches. +// Locking is left to the user. +func (h *HeaderHashes) getLocalHeaderHash(i uint32) (util.Uint256, bool) { + if i > h.lastHeaderIndex() { + return util.Uint256{}, false + } + if i >= h.storedHeaderCount { + return h.latest[i-h.storedHeaderCount], true + } + previousStored := h.storedHeaderCount - headerBatchCount + if i >= previousStored { + return h.previous[i-previousStored], true + } + return util.Uint256{}, false +} + +func (h *HeaderHashes) haveRecentHash(hash util.Uint256, i uint32) bool { + h.lock.RLock() + defer h.lock.RUnlock() + for ; i > 0; i-- { + lh, ok := h.getLocalHeaderHash(i) + if ok && hash.Equals(lh) { + return true + } + } + return false +} diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 63134688a..fc74f3849 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -59,7 +59,7 @@ func (bc *Blockchain) newBlock(txs ...*transaction.Transaction) *block.Block { lastBlock, ok := bc.topBlock.Load().(*block.Block) if !ok { var err error - lastBlock, err = bc.GetBlock(bc.GetHeaderHash(int(bc.BlockHeight()))) + lastBlock, err = bc.GetBlock(bc.GetHeaderHash(bc.BlockHeight())) if err != nil { panic(err) } diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 85daf01ae..616ccfffd 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -40,7 +40,7 @@ type Ledger interface { CurrentBlockHash() util.Uint256 GetBlock(hash util.Uint256) (*block.Block, error) GetConfig() config.ProtocolConfiguration - GetHeaderHash(int) util.Uint256 + GetHeaderHash(uint32) util.Uint256 } // Context represents context in which interops are executed. @@ -377,7 +377,7 @@ func (ic *Context) BlockHeight() uint32 { // CurrentBlockHash returns current block hash got from Context's block if it's set. func (ic *Context) CurrentBlockHash() util.Uint256 { if ic.Block != nil { - return ic.Chain.GetHeaderHash(int(ic.Block.Index - 1)) // Persisting block is not yet stored. + return ic.Chain.GetHeaderHash(ic.Block.Index - 1) // Persisting block is not yet stored. } return ic.Chain.CurrentBlockHash() } diff --git a/pkg/core/native/ledger.go b/pkg/core/native/ledger.go index 708bc7e6d..51734c226 100644 --- a/pkg/core/native/ledger.go +++ b/pkg/core/native/ledger.go @@ -196,7 +196,7 @@ func getBlockHashFromItem(ic *interop.Context, item stackitem.Item) util.Uint256 if uint32(index) > ic.BlockHeight() { panic(fmt.Errorf("no block with index %d", index)) } - return ic.Chain.GetHeaderHash(int(index)) + return ic.Chain.GetHeaderHash(uint32(index)) } hash, err := getUint256FromItem(item) if err != nil { diff --git a/pkg/core/native/native_test/ledger_test.go b/pkg/core/native/native_test/ledger_test.go index 83455f4c5..1208aa298 100644 --- a/pkg/core/native/native_test/ledger_test.go +++ b/pkg/core/native/native_test/ledger_test.go @@ -111,7 +111,7 @@ func TestLedger_GetTransactionFromBlock(t *testing.T) { ledgerInvoker := c.WithSigners(c.Committee) ledgerInvoker.Invoke(t, e.Chain.BlockHeight(), "currentIndex") // Adds a block. - b := e.GetBlockByIndex(t, int(e.Chain.BlockHeight())) + b := e.GetBlockByIndex(t, e.Chain.BlockHeight()) check := func(t testing.TB, stack []stackitem.Item) { require.Equal(t, 1, len(stack)) @@ -148,8 +148,8 @@ func TestLedger_GetBlock(t *testing.T) { e := c.Executor ledgerInvoker := c.WithSigners(c.Committee) - ledgerInvoker.Invoke(t, e.Chain.GetHeaderHash(int(e.Chain.BlockHeight())).BytesBE(), "currentHash") // Adds a block. - b := e.GetBlockByIndex(t, int(e.Chain.BlockHeight())) + ledgerInvoker.Invoke(t, e.Chain.GetHeaderHash(e.Chain.BlockHeight()).BytesBE(), "currentHash") // Adds a block. + b := e.GetBlockByIndex(t, e.Chain.BlockHeight()) expected := []stackitem.Item{ stackitem.NewByteArray(b.Hash().BytesBE()), diff --git a/pkg/core/prometheus.go b/pkg/core/prometheus.go index b81fb847d..f47b60e20 100644 --- a/pkg/core/prometheus.go +++ b/pkg/core/prometheus.go @@ -44,7 +44,7 @@ func updatePersistedHeightMetric(pHeight uint32) { persistedHeight.Set(float64(pHeight)) } -func updateHeaderHeightMetric(hHeight int) { +func updateHeaderHeightMetric(hHeight uint32) { headerHeight.Set(float64(hHeight)) } diff --git a/pkg/core/statesync/module.go b/pkg/core/statesync/module.go index 38d603e1d..58ce4c0cb 100644 --- a/pkg/core/statesync/module.go +++ b/pkg/core/statesync/module.go @@ -66,7 +66,7 @@ type Ledger interface { BlockHeight() uint32 GetConfig() config.ProtocolConfiguration GetHeader(hash util.Uint256) (*block.Header, error) - GetHeaderHash(int) util.Uint256 + GetHeaderHash(uint32) util.Uint256 HeaderHeight() uint32 } @@ -214,7 +214,7 @@ func (s *Module) defineSyncStage() error { s.log.Info("MPT is in sync", zap.Uint32("stateroot height", s.stateMod.CurrentLocalHeight())) } else if s.syncStage&headersSynced != 0 { - header, err := s.bc.GetHeader(s.bc.GetHeaderHash(int(s.syncPoint + 1))) + header, err := s.bc.GetHeader(s.bc.GetHeaderHash(s.syncPoint + 1)) if err != nil { return fmt.Errorf("failed to get header to initialize MPT billet: %w", err) } diff --git a/pkg/core/statesync/neotest_test.go b/pkg/core/statesync/neotest_test.go index d799d9afa..31a7a3589 100644 --- a/pkg/core/statesync/neotest_test.go +++ b/pkg/core/statesync/neotest_test.go @@ -16,9 +16,9 @@ import ( ) func TestStateSyncModule_Init(t *testing.T) { - var ( - stateSyncInterval = 2 - maxTraceable uint32 = 3 + const ( + stateSyncInterval = 2 + maxTraceable = 3 ) spoutCfg := func(c *config.ProtocolConfiguration) { c.StateRootInHeader = true @@ -55,7 +55,7 @@ func TestStateSyncModule_Init(t *testing.T) { t.Run("inactive: bolt chain height is close enough to spout chain height", func(t *testing.T) { bcBolt, _, _ := chain.NewMultiWithCustomConfig(t, boltCfg) - for i := 1; i < int(bcSpout.BlockHeight())-stateSyncInterval; i++ { + for i := uint32(1); i < bcSpout.BlockHeight()-stateSyncInterval; i++ { b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(i)) require.NoError(t, err) require.NoError(t, bcBolt.AddBlock(b)) @@ -114,9 +114,9 @@ func TestStateSyncModule_Init(t *testing.T) { require.NoError(t, module.Init(bcSpout.BlockHeight())) // firstly, fetch all headers to create proper DB state (where headers are in sync) - stateSyncPoint := (int(bcSpout.BlockHeight()) / stateSyncInterval) * stateSyncInterval + stateSyncPoint := (bcSpout.BlockHeight() / stateSyncInterval) * stateSyncInterval var expectedHeader *block.Header - for i := 1; i <= int(bcSpout.HeaderHeight()); i++ { + for i := uint32(1); i <= bcSpout.HeaderHeight(); i++ { header, err := bcSpout.GetHeader(bcSpout.GetHeaderHash(i)) require.NoError(t, err) require.NoError(t, module.AddHeaders(header)) @@ -142,7 +142,7 @@ func TestStateSyncModule_Init(t *testing.T) { require.Equal(t, expectedHeader.PrevStateRoot, unknownNodes[0]) // add several blocks to create DB state where blocks are not in sync yet, but it's not a genesis. - for i := stateSyncPoint - int(maxTraceable) + 1; i <= stateSyncPoint-stateSyncInterval-1; i++ { + for i := stateSyncPoint - maxTraceable + 1; i <= stateSyncPoint-stateSyncInterval-1; i++ { block, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(i)) require.NoError(t, err) require.NoError(t, module.AddBlock(block)) @@ -283,10 +283,10 @@ func TestStateSyncModule_Init(t *testing.T) { func TestStateSyncModule_RestoreBasicChain(t *testing.T) { check := func(t *testing.T, spoutEnableGC bool) { - var ( - stateSyncInterval = 4 - maxTraceable uint32 = 6 - stateSyncPoint = 24 + const ( + stateSyncInterval = 4 + maxTraceable = 6 + stateSyncPoint = 24 ) spoutCfg := func(c *config.ProtocolConfiguration) { c.KeepOnlyLatestState = spoutEnableGC @@ -325,7 +325,7 @@ func TestStateSyncModule_RestoreBasicChain(t *testing.T) { require.Error(t, module.AddHeaders(h)) }) t.Run("no error: add blocks before initialisation", func(t *testing.T) { - b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(int(bcSpout.BlockHeight()))) + b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(bcSpout.BlockHeight())) require.NoError(t, err) require.NoError(t, module.AddBlock(b)) }) @@ -342,7 +342,7 @@ func TestStateSyncModule_RestoreBasicChain(t *testing.T) { // add headers to module headers := make([]*block.Header, 0, bcSpout.HeaderHeight()) for i := uint32(1); i <= bcSpout.HeaderHeight(); i++ { - h, err := bcSpout.GetHeader(bcSpout.GetHeaderHash(int(i))) + h, err := bcSpout.GetHeader(bcSpout.GetHeaderHash(i)) require.NoError(t, err) headers = append(headers, h) } @@ -355,7 +355,7 @@ func TestStateSyncModule_RestoreBasicChain(t *testing.T) { // add blocks t.Run("error: unexpected block index", func(t *testing.T) { - b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(stateSyncPoint - int(maxTraceable))) + b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(stateSyncPoint - maxTraceable)) require.NoError(t, err) require.Error(t, module.AddBlock(b)) }) @@ -379,7 +379,7 @@ func TestStateSyncModule_RestoreBasicChain(t *testing.T) { require.Error(t, module.AddBlock(b)) }) - for i := stateSyncPoint - int(maxTraceable) + 1; i <= stateSyncPoint; i++ { + for i := uint32(stateSyncPoint - maxTraceable + 1); i <= stateSyncPoint; i++ { b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(i)) require.NoError(t, err) require.NoError(t, module.AddBlock(b)) @@ -432,7 +432,7 @@ func TestStateSyncModule_RestoreBasicChain(t *testing.T) { require.Equal(t, uint32(stateSyncPoint), bcBolt.BlockHeight()) // add missing blocks to bcBolt: should be ok, because state is synced - for i := stateSyncPoint + 1; i <= int(bcSpout.BlockHeight()); i++ { + for i := uint32(stateSyncPoint + 1); i <= bcSpout.BlockHeight(); i++ { b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(i)) require.NoError(t, err) require.NoError(t, bcBolt.AddBlock(b)) diff --git a/pkg/core/util.go b/pkg/core/util.go index 8ba894c52..2694e8885 100644 --- a/pkg/core/util.go +++ b/pkg/core/util.go @@ -64,8 +64,8 @@ func getNextConsensusAddress(validators []*keys.PublicKey) (val util.Uint160, er return hash.Hash160(raw), nil } -// headerSliceReverse reverses the given slice of *Header. -func headerSliceReverse(dest []*block.Header) { +// hashSliceReverse reverses the given slice of util.Uint256. +func hashSliceReverse(dest []util.Uint256) { for i, j := 0, len(dest)-1; i < j; i, j = i+1, j-1 { dest[i], dest[j] = dest[j], dest[i] } diff --git a/pkg/neotest/basic.go b/pkg/neotest/basic.go index b0653c940..8c1c5b307 100644 --- a/pkg/neotest/basic.go +++ b/pkg/neotest/basic.go @@ -52,7 +52,7 @@ func NewExecutor(t testing.TB, bc *core.Blockchain, validator, committee Signer) // TopBlock returns the block with the highest index. func (e *Executor) TopBlock(t testing.TB) *block.Block { - b, err := e.Chain.GetBlock(e.Chain.GetHeaderHash(int(e.Chain.BlockHeight()))) + b, err := e.Chain.GetBlock(e.Chain.GetHeaderHash(e.Chain.BlockHeight())) require.NoError(t, err) return b } @@ -361,7 +361,7 @@ func (e *Executor) AddBlockCheckHalt(t testing.TB, txs ...*transaction.Transacti // TestInvoke creates a test VM with a dummy block and executes a transaction in it. func TestInvoke(bc *core.Blockchain, tx *transaction.Transaction) (*vm.VM, error) { - lastBlock, err := bc.GetBlock(bc.GetHeaderHash(int(bc.BlockHeight()))) + lastBlock, err := bc.GetBlock(bc.GetHeaderHash(bc.BlockHeight())) if err != nil { return nil, err } @@ -392,7 +392,7 @@ func (e *Executor) GetTransaction(t testing.TB, h util.Uint256) (*transaction.Tr } // GetBlockByIndex returns a block by the specified index. -func (e *Executor) GetBlockByIndex(t testing.TB, idx int) *block.Block { +func (e *Executor) GetBlockByIndex(t testing.TB, idx uint32) *block.Block { h := e.Chain.GetHeaderHash(idx) require.NotEmpty(t, h) b, err := e.Chain.GetBlock(h) diff --git a/pkg/network/server.go b/pkg/network/server.go index 4a8512e11..11737d61b 100644 --- a/pkg/network/server.go +++ b/pkg/network/server.go @@ -61,7 +61,7 @@ type ( GetBlock(hash util.Uint256) (*block.Block, error) GetConfig() config.ProtocolConfiguration GetHeader(hash util.Uint256) (*block.Header, error) - GetHeaderHash(int) util.Uint256 + GetHeaderHash(uint32) util.Uint256 GetMaxVerificationGAS() int64 GetMemPool() *mempool.Pool GetNotaryBalance(acc util.Uint160) *big.Int @@ -972,7 +972,7 @@ func (s *Server) handleGetBlocksCmd(p Peer, gb *payload.GetBlocks) error { } blockHashes := make([]util.Uint256, 0) for i := start.Index + 1; i <= start.Index+uint32(count); i++ { - hash := s.chain.GetHeaderHash(int(i)) + hash := s.chain.GetHeaderHash(i) if hash.Equals(util.Uint256{}) { break } @@ -995,7 +995,7 @@ func (s *Server) handleGetBlockByIndexCmd(p Peer, gbd *payload.GetBlockByIndex) count = payload.MaxHashesCount } for i := gbd.IndexStart; i < gbd.IndexStart+uint32(count); i++ { - hash := s.chain.GetHeaderHash(int(i)) + hash := s.chain.GetHeaderHash(i) if hash.Equals(util.Uint256{}) { break } @@ -1026,7 +1026,7 @@ func (s *Server) handleGetHeadersCmd(p Peer, gh *payload.GetBlockByIndex) error resp := payload.Headers{} resp.Hdrs = make([]*block.Header, 0, count) for i := gh.IndexStart; i < gh.IndexStart+uint32(count); i++ { - hash := s.chain.GetHeaderHash(int(i)) + hash := s.chain.GetHeaderHash(i) if hash.Equals(util.Uint256{}) { break } diff --git a/pkg/services/rpcsrv/client_test.go b/pkg/services/rpcsrv/client_test.go index b93ac2e78..f9749d261 100644 --- a/pkg/services/rpcsrv/client_test.go +++ b/pkg/services/rpcsrv/client_test.go @@ -1272,7 +1272,7 @@ func TestInvokeVerify(t *testing.T) { }) t.Run("positive, historic, by block, with signer", func(t *testing.T) { - res, err := c.InvokeContractVerifyWithState(chain.GetHeaderHash(int(chain.BlockHeight())-1), contract, []smartcontract.Parameter{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}}) + res, err := c.InvokeContractVerifyWithState(chain.GetHeaderHash(chain.BlockHeight()-1), contract, []smartcontract.Parameter{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}}) require.NoError(t, err) require.Equal(t, "HALT", res.State) require.Equal(t, 1, len(res.Stack)) diff --git a/pkg/services/rpcsrv/server.go b/pkg/services/rpcsrv/server.go index 349c2676e..b0dd254b4 100644 --- a/pkg/services/rpcsrv/server.go +++ b/pkg/services/rpcsrv/server.go @@ -78,7 +78,7 @@ type ( GetEnrollments() ([]state.Validator, error) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32) GetHeader(hash util.Uint256) (*block.Header, error) - GetHeaderHash(int) util.Uint256 + GetHeaderHash(uint32) util.Uint256 GetMaxVerificationGAS() int64 GetMemPool() *mempool.Pool GetNEP11Contracts() []util.Uint160 @@ -652,7 +652,7 @@ func (s *Server) fillBlockMetadata(obj io.Serializable, h *block.Header) result. Confirmations: s.chain.BlockHeight() - h.Index + 1, } - hash := s.chain.GetHeaderHash(int(h.Index) + 1) + hash := s.chain.GetHeaderHash(h.Index + 1) if !hash.Equals(util.Uint256{}) { res.NextBlockHash = &hash } @@ -1646,7 +1646,7 @@ func (s *Server) getrawtransaction(reqParams params.Params) (interface{}, *neorp if height == math.MaxUint32 { // Mempooled transaction. return res, nil } - _header := s.chain.GetHeaderHash(int(height)) + _header := s.chain.GetHeaderHash(height) header, err := s.chain.GetHeader(_header) if err != nil { return nil, neorpc.NewRPCError("Failed to get header for the transaction", err.Error()) @@ -2037,15 +2037,12 @@ func (s *Server) getHistoricParams(reqParams params.Params) (uint32, *neorpc.Err if err != nil { return 0, neorpc.NewInvalidParamsError(fmt.Sprintf("unknown block or stateroot: %s", err)) } - height = int(stateH) + height = stateH } else { - height = int(b.Index) + height = b.Index } } - if height > math.MaxUint32 { - return 0, neorpc.NewInvalidParamsError("historic height exceeds max uint32 value") - } - return uint32(height) + 1, nil + return height + 1, nil } func (s *Server) prepareInvocationContext(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction, nextH *uint32, verbose bool) (*interop.Context, *neorpc.Error) { @@ -2683,16 +2680,16 @@ drainloop: close(s.notaryRequestCh) } -func (s *Server) blockHeightFromParam(param *params.Param) (int, *neorpc.Error) { +func (s *Server) blockHeightFromParam(param *params.Param) (uint32, *neorpc.Error) { num, err := param.GetInt() if err != nil { return 0, neorpc.ErrInvalidParams } - if num < 0 || num > int(s.chain.BlockHeight()) { + if num < 0 || int64(num) > int64(s.chain.BlockHeight()) { return 0, invalidBlockHeightError(0, num) } - return num, nil + return uint32(num), nil } func (s *Server) packResponse(r *params.In, result interface{}, respErr *neorpc.Error) abstract { diff --git a/pkg/services/rpcsrv/server_test.go b/pkg/services/rpcsrv/server_test.go index ec6270e86..134dc4dd8 100644 --- a/pkg/services/rpcsrv/server_test.go +++ b/pkg/services/rpcsrv/server_test.go @@ -2271,7 +2271,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] }) t.Run("verbose != 0", func(t *testing.T) { - nextHash := chain.GetHeaderHash(int(hdr.Index) + 1) + nextHash := chain.GetHeaderHash(hdr.Index + 1) expected := &result.Header{ Header: *hdr, BlockMetadata: result.BlockMetadata{ @@ -2315,7 +2315,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] testNEP17T := func(t *testing.T, start, stop, limit, page int, sent, rcvd []int) { ps := []string{`"` + testchain.PrivateKeyByID(0).Address() + `"`} if start != 0 { - h, err := e.chain.GetHeader(e.chain.GetHeaderHash(start)) + h, err := e.chain.GetHeader(e.chain.GetHeaderHash(uint32(start))) var ts uint64 if err == nil { ts = h.Timestamp @@ -2325,7 +2325,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] ps = append(ps, strconv.FormatUint(ts, 10)) } if stop != 0 { - h, err := e.chain.GetHeader(e.chain.GetHeaderHash(stop)) + h, err := e.chain.GetHeader(e.chain.GetHeaderHash(uint32(stop))) var ts uint64 if err == nil { ts = h.Timestamp @@ -2846,7 +2846,7 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc rublesHash, err := util.Uint160DecodeStringLE(testContractHash) require.NoError(t, err) - blockWithFAULTedTx, err := e.chain.GetBlock(e.chain.GetHeaderHash(int(faultedTxBlock))) // Transaction with ABORT inside. + blockWithFAULTedTx, err := e.chain.GetBlock(e.chain.GetHeaderHash(faultedTxBlock)) // Transaction with ABORT inside. require.NoError(t, err) require.Equal(t, 1, len(blockWithFAULTedTx.Transactions)) txFAULTed := blockWithFAULTedTx.Transactions[0] diff --git a/scripts/gendump/main.go b/scripts/gendump/main.go index 6f79ff917..4e7a0d309 100644 --- a/scripts/gendump/main.go +++ b/scripts/gendump/main.go @@ -66,7 +66,7 @@ func main() { handleError("can't get next block validators", err) valScript, err := smartcontract.CreateDefaultMultiSigRedeemScript(nbVals) handleError("can't create verification script", err) - lastBlock, err := bc.GetBlock(bc.GetHeaderHash(int(bc.BlockHeight()))) + lastBlock, err := bc.GetBlock(bc.GetHeaderHash(bc.BlockHeight())) handleError("can't fetch last block", err) txMoveNeo, err := testchain.NewTransferFromOwner(bc, bc.GoverningTokenHash(), h, native.NEOTotalSupply, 0, 2)