diff --git a/itests/config/blockchain.go b/itests/config/blockchain.go index ca6f42f672..397d19ecd2 100644 --- a/itests/config/blockchain.go +++ b/itests/config/blockchain.go @@ -115,6 +115,8 @@ func NewBlockchainConfig(options ...BlockchainOption) (*BlockchainConfig, error) bs.BlockRewardTerm = defaultBlockRewardTerm bs.MinXTNBuyBackPeriod = defaultMinXTNBuyBackPeriod bs.LightNodeBlockFieldsAbsenceInterval = lightNodeBlockFieldsAbsenceInterval + bs.GenerationPeriod = defaultGenerationPeriod + bs.MaxEndorsements = defaultMaxEndorsements cfg := &BlockchainConfig{ Settings: bs, diff --git a/itests/config/genesis_settings.go b/itests/config/genesis_settings.go index 8282d9081a..4232696a2a 100644 --- a/itests/config/genesis_settings.go +++ b/itests/config/genesis_settings.go @@ -38,6 +38,9 @@ const ( defaultQuorum = 1 // Default quorum is 1 to allow mining without waiting for testnet client. lightNodeBlockFieldsAbsenceInterval = 2 + + defaultGenerationPeriod = 5 + defaultMaxEndorsements = 3 ) var ( diff --git a/pkg/api/node_api.go b/pkg/api/node_api.go index ee9266f764..7e123a87b3 100644 --- a/pkg/api/node_api.go +++ b/pkg/api/node_api.go @@ -623,14 +623,6 @@ func (a *NodeApi) PeersConnected(w http.ResponseWriter, _ *http.Request) error { return nil } -func (a *NodeApi) PeersSuspended(w http.ResponseWriter, _ *http.Request) error { - rs := a.app.PeersSuspended() - if err := trySendJSON(w, rs); err != nil { - return errors.Wrap(err, "PeersSuspended") - } - return nil -} - func (a *NodeApi) PeersBlackListed(w http.ResponseWriter, _ *http.Request) error { rs := a.app.PeersBlackListed() if err := trySendJSON(w, rs); err != nil { @@ -906,13 +898,6 @@ func (a *NodeApi) snapshotStateHash(w http.ResponseWriter, r *http.Request) erro return nil } -// TODO: Move JSON tags to GeneratorInfo structure. -type generatorInfo struct { - Address string `json:"address"` - Balance uint64 `json:"balance"` - TransactionID string `json:"transactionID"` -} - func (a *NodeApi) GeneratorsAt(w http.ResponseWriter, r *http.Request) error { heightStr := chi.URLParam(r, "height") height, err := strconv.ParseUint(heightStr, 10, 64) @@ -935,32 +920,31 @@ func (a *NodeApi) GeneratorsAt(w http.ResponseWriter, r *http.Request) error { if err != nil { return err } - - infos := make([]generatorInfo, len(gs)) - for i, g := range gs { - infos[i] = generatorInfo{ - Address: g.Address().String(), - Balance: g.GenerationBalance(), - TransactionID: "", // It was decided to leave it empty. - } - } - return trySendJSON(w, infos) + return trySendJSON(w, gs) } func (a *NodeApi) FinalizedHeight(w http.ResponseWriter, _ *http.Request) error { h, err := a.state.LastFinalizedHeight() if err != nil { - return err + return fmt.Errorf("failed to get finalized block height: %w", err) } return trySendJSON(w, map[string]uint64{"height": h}) } func (a *NodeApi) FinalizedHeader(w http.ResponseWriter, _ *http.Request) error { - blockHeader, err := a.app.state.LastFinalizedBlock() + fh, err := a.state.LastFinalizedHeight() if err != nil { - return err + return fmt.Errorf("failed to get finalized block height: %w", err) + } + header, err := a.app.state.LastFinalizedBlock() + if err != nil { + return fmt.Errorf("failed to get finalized block header: %w", err) + } + b, err := newAPIBlockFromHeader(*header, a.app.scheme(), fh) + if err != nil { + return fmt.Errorf("failed to get finalized block: %w", err) } - return trySendJSON(w, blockHeader) + return trySendJSON(w, b) } type signTxEnvelope struct { diff --git a/pkg/api/peers.go b/pkg/api/peers.go index ac7f47ac5d..8cd61c797f 100644 --- a/pkg/api/peers.go +++ b/pkg/api/peers.go @@ -26,12 +26,8 @@ type PeersKnown struct { // PeersAll is a list of all known not banned, not suspended and not blacklisted peers with a publicly // available declared address. func (a *App) PeersAll() (PeersKnown, error) { - suspended := a.peers.Suspended() blackList := a.peers.BlackList() - restrictedIPsMap := make(map[string]struct{}, len(suspended)+len(blackList)) - for _, suspendedPeer := range suspended { - restrictedIPsMap[suspendedPeer.IP.String()] = struct{}{} - } + restrictedIPsMap := make(map[string]struct{}, len(blackList)) for _, blackListedPeer := range blackList { restrictedIPsMap[blackListedPeer.IP.String()] = struct{}{} } @@ -144,21 +140,6 @@ type RestrictedPeerInfo struct { Reason string `json:"reason,omitempty"` } -func (a *App) PeersSuspended() []RestrictedPeerInfo { - suspended := a.peers.Suspended() - - out := make([]RestrictedPeerInfo, 0, len(suspended)) - for _, p := range suspended { - out = append(out, RestrictedPeerInfo{ - Hostname: "/" + p.IP.String(), - Timestamp: p.RestrictTimestampMillis, - Reason: p.Reason, - }) - } - - return out -} - func (a *App) PeersBlackListed() []RestrictedPeerInfo { blackList := a.peers.BlackList() diff --git a/pkg/api/peers_test.go b/pkg/api/peers_test.go index 232f0bf850..5fded55a2d 100644 --- a/pkg/api/peers_test.go +++ b/pkg/api/peers_test.go @@ -33,50 +33,6 @@ func TestApp_PeersKnown(t *testing.T) { require.Len(t, rs2.Peers, 1) } -func TestApp_PeersSuspended(t *testing.T) { - peerManager := peers.NewMockPeerManager(t) - - now := time.Now() - cfg := &settings.BlockchainSettings{ - FunctionalitySettings: settings.FunctionalitySettings{ - GenerationPeriod: 0, - }, - } - - ips := []string{"13.3.4.1", "5.3.6.7"} - testData := []storage.SuspendedPeer{ - { - IP: storage.IPFromString(ips[0]), - RestrictTimestampMillis: now.Add(time.Minute).UnixMilli(), - RestrictDuration: time.Minute, - Reason: "some reason #1", - }, - { - IP: storage.IPFromString(ips[1]), - RestrictTimestampMillis: now.Add(2 * time.Minute).UnixMilli(), - RestrictDuration: time.Minute, - Reason: "some reason #2", - }, - } - - peerManager.EXPECT().Suspended().Return(testData) - - app, err := NewApp("key", nil, services.Services{Peers: peerManager}, cfg) - require.NoError(t, err) - - suspended := app.PeersSuspended() - - for i, actual := range suspended { - p := testData[i] - expected := RestrictedPeerInfo{ - Hostname: "/" + ips[i], - Timestamp: p.RestrictTimestampMillis, - Reason: p.Reason, - } - assert.Equal(t, expected, actual) - } -} - func TestApp_PeersBlackList(t *testing.T) { peerManager := peers.NewMockPeerManager(t) diff --git a/pkg/api/routes.go b/pkg/api/routes.go index 452440fddb..0b3a3844e0 100644 --- a/pkg/api/routes.go +++ b/pkg/api/routes.go @@ -152,7 +152,6 @@ func (a *NodeApi) routes(opts *RunOptions) (chi.Router, error) { r.Route("/peers", func(r chi.Router) { r.Get("/all", wrapper(a.PeersAll)) r.Get("/connected", wrapper(a.PeersConnected)) - r.Get("/suspended", wrapper(a.PeersSuspended)) r.Get("/blacklisted", wrapper(a.PeersBlackListed)) rAuth := r.With(checkAuthMiddleware) diff --git a/pkg/node/fsm/fsm_common.go b/pkg/node/fsm/fsm_common.go index 8f07f42782..2557f553d8 100644 --- a/pkg/node/fsm/fsm_common.go +++ b/pkg/node/fsm/fsm_common.go @@ -96,12 +96,9 @@ func syncWithNewPeer(state State, baseInfo BaseInfo, p peer.Peer) (State, Async, lastSignatures, baseInfo.enableLightMode, ) - c := conf{ - peerSyncWith: p, - timeout: defaultSyncTimeout, - } baseInfo.logger.Debug("Starting synchronization with peer", "state", state.String(), "peer", p.ID()) baseInfo.syncPeer.SetPeer(p) + c := conf{timeout: defaultSyncTimeout} return &SyncState{ baseInfo: baseInfo, conf: c.Now(baseInfo.tm), diff --git a/pkg/node/fsm/idle_state.go b/pkg/node/fsm/idle_state.go index 1f4c314583..30ae2964c7 100644 --- a/pkg/node/fsm/idle_state.go +++ b/pkg/node/fsm/idle_state.go @@ -2,6 +2,7 @@ package fsm import ( "context" + "log/slog" "github.com/pkg/errors" "github.com/qmuntal/stateless" @@ -49,6 +50,8 @@ func (a *IdleState) StartMining() (State, Async, error) { func (a *IdleState) MinedBlock( block *proto.Block, limits proto.MiningLimits, keyPair proto.KeyPair, vrf []byte, ) (State, Async, error) { + a.baseInfo.logger.Info("New block mined", slog.String("state", a.String()), + slog.String("blockID", block.ID.String())) newA, ok := newNGState(a.baseInfo).(*NGState) if !ok { return a, nil, a.Errorf(errors.Errorf("unexpected type '%T' expected '*NGState'", a.baseInfo)) diff --git a/pkg/node/fsm/ng_state.go b/pkg/node/fsm/ng_state.go index ea90c74e52..357865d879 100644 --- a/pkg/node/fsm/ng_state.go +++ b/pkg/node/fsm/ng_state.go @@ -4,14 +4,16 @@ import ( "context" "fmt" "log/slog" + "time" "github.com/pkg/errors" "github.com/qmuntal/stateless" - "github.com/wavesplatform/gowaves/pkg/crypto" - "github.com/ccoveille/go-safecast/v2" + "github.com/wavesplatform/gowaves/pkg/crypto" + "github.com/wavesplatform/gowaves/pkg/errs" + "github.com/wavesplatform/gowaves/pkg/crypto/bls" "github.com/wavesplatform/gowaves/pkg/logging" "github.com/wavesplatform/gowaves/pkg/metrics" @@ -185,24 +187,14 @@ func (a *NGState) Block(peer peer.Peer, block *proto.Block) (State, Async, error blockHeight := height + 1 top := a.baseInfo.storage.TopBlock() - if top.BlockID() != block.Parent { // does block refer to last block - a.baseInfo.logger.Debug("Key-block has parent which is not the top block", "state", a.String(), - "blockID", block.ID.String(), "parent", block.Parent.String(), "top", top.ID.String()) - if a.baseInfo.enableLightMode { - if err = a.rollbackToStateFromCacheInLightNode(block.Parent); err != nil { - return a, nil, a.Errorf(err) - } - } else { - if blockFromCache, okGet := a.blocksCache.Get(block.Parent); okGet { - a.baseInfo.logger.Debug("Re-applying block from cache", "state", a.String(), - "blockID", blockFromCache.ID.String()) - if err = a.rollbackToStateFromCache(blockFromCache); err != nil { - return a, nil, a.Errorf(err) - } - } + // Check if the parent on the top of blockchain. + if top.BlockID() != block.Parent { + // The block does not refer to last block, try to lookup for the parent in the blocks cache. + if rpErr := a.restoreParent(block, top); rpErr != nil { + return a, nil, a.Errorf(rpErr) } } - + // The parent is on the top of the blockchain. if a.baseInfo.enableLightMode { defer func() { pe := extension.NewPeerExtension(peer, a.baseInfo.scheme, a.baseInfo.netLogger) @@ -216,16 +208,26 @@ func (a *NGState) Block(peer peer.Peer, block *proto.Block) (State, Async, error []*proto.Block{block}, ) if err != nil { + // Suspend peer if not empty. + if a.baseInfo.syncPeer != nil { + if errs.IsValidationError(err) || errs.IsValidationError(errors.Cause(err)) { + a.baseInfo.logger.Debug("Suspending peer because of blocks application error", + slog.String("state", a.String()), + slog.String("peer", a.baseInfo.syncPeer.GetPeer().ID().String()), logging.Error(err)) + a.baseInfo.peers.AddToBlackList(a.baseInfo.syncPeer.GetPeer(), time.Now(), err.Error()) + } + } return a, nil, a.Errorf(errors.Wrapf(err, "failed to apply block %s", block.BlockID())) } metrics.BlockApplied(block, blockHeight) a.baseInfo.endorsements.CleanAll() - parentBlock, err := a.baseInfo.storage.Block(block.Parent) + // Retrieve parent's block header, we need it to save generator's public key in endorsements manager. + parentHeader, err := a.baseInfo.storage.Header(block.Parent) if err != nil { return a, nil, a.Errorf(errors.Wrapf(err, "failed to retrieve parent block %s", block.Parent)) } - a.baseInfo.endorsements.SaveBlockGenerator(&parentBlock.GeneratorPublicKey) + a.baseInfo.endorsements.SaveBlockGenerator(&parentHeader.GeneratorPublicKey) a.blocksCache.Clear() a.blocksCache.AddBlockState(block) @@ -253,21 +255,53 @@ func (a *NGState) Block(peer peer.Peer, block *proto.Block) (State, Async, error return newNGState(a.baseInfo), nil, nil } +func (a *NGState) restoreParent(block, top *proto.Block) error { + a.baseInfo.logger.Debug("Key-block has parent which is not the top block", + slog.String("state", a.String()), slog.String("blockID", block.ID.String()), + slog.String("parent", block.Parent.String()), slog.String("top", top.ID.String())) + if cachedBlock, cacheHit := a.blocksCache.Get(block.Parent); cacheHit { + // The parent found in the cache, re-apply it to the blockchain. + a.baseInfo.logger.Debug("Re-applying block from cache", slog.String("state", a.String()), + slog.String("blockID", cachedBlock.ID.String())) + var reApplyErr error + if a.baseInfo.enableLightMode { + reApplyErr = a.rollbackToStateFromCacheInLightNode(block.Parent) + } else { + reApplyErr = a.rollbackToStateFromCache(cachedBlock) + } + return reApplyErr + } + return nil +} + func (a *NGState) endorseParentWithEachKey( sks []bls.SecretKey, block *proto.Block, blockHeight proto.Height, ) error { - activationHeight, err := a.baseInfo.storage.ActivationHeight(int16(settings.DeterministicFinality)) - if err != nil { - return a.Errorf(errors.Wrapf(err, "failed to get activation height for finality %s", block.BlockID())) + // Check current generators set. + gs, err := a.baseInfo.storage.CommittedGenerators(blockHeight) + if err != nil && !errors.Is(err, state.ErrNoGeneratorsSet) { + return a.Errorf(errors.Wrapf(err, "failed to get generators for block '%s'", block.BlockID())) } - - periodStart, err := state.CurrentGenerationPeriodStart(activationHeight, blockHeight, a.baseInfo.generationPeriod) - if err != nil { - return a.Errorf(errors.Wrapf(err, "failed to get current generation period, block %s", block.BlockID())) + if errors.Is(err, state.ErrNoGeneratorsSet) || len(gs) == 0 { + slog.Debug("Generator set is empty, skipping block endorsement", slog.Uint64("height", blockHeight)) + return nil } + // Following variables are used for logging only. + var periodStart uint32 + if slog.Default().Enabled(context.Background(), slog.LevelDebug) { + activationHeight, ahErr := a.baseInfo.storage.ActivationHeight(int16(settings.DeterministicFinality)) + if ahErr != nil { + return a.Errorf(errors.Wrapf(ahErr, "failed to get activation height for finality %s", block.BlockID())) + } + var gpErr error + periodStart, gpErr = state.CurrentGenerationPeriodStart(activationHeight, blockHeight, a.baseInfo.generationPeriod) + if gpErr != nil { + return a.Errorf(errors.Wrapf(gpErr, "failed to get current generation period, block %s", block.BlockID())) + } + } for i := range sks { sk := sks[i] pk, pkErr := sk.PublicKey() @@ -278,19 +312,19 @@ func (a *NGState) endorseParentWithEachKey( slog.Int("SeedIndex", i), slog.String("BLS PublicKey", pk.String()), slog.Any("BlockID", block.BlockID()), slog.Any("GenerationPeriodStart", periodStart)) - g, gErr := a.baseInfo.storage.FindGenerator(state.ByBLSPublicKey(pk)) + g, gErr := a.baseInfo.storage.FindGenerator(blockHeight, state.ByBLSPublicKey(pk)) if gErr != nil { slog.Warn("Wallet's BLS public key is not in the generators set", slog.String("BLS PublicKey", pk.String()), logging.Error(gErr)) continue } - if g.GenerationBalance() == 0 { + if g.Balance == 0 { slog.Debug("Wallet's BLS public key has insufficient generation balance", slog.Int("SeedIndex", i), slog.String("BLS PublicKey", pk.String()), slog.Any("BlockID", block.BlockID()), slog.Any("GenerationPeriodStart", periodStart)) continue } - if enErr := a.Endorse(block.Parent, g, sk); enErr != nil { + if enErr := a.Endorse(blockHeight, block.Parent, g, sk); enErr != nil { return a.Errorf(errors.Wrapf(enErr, "failed to endorse parent block")) } } @@ -316,12 +350,15 @@ func (a *NGState) BlockEndorsement(blockEndorsement *proto.BlockEndorsement) (St } top := a.baseInfo.storage.TopBlock() - + h, err := a.baseInfo.storage.Height() + if err != nil { + return a, nil, a.Errorf(errors.Wrapf(err, "failed to retrieve current height")) + } generatorIndex, err := safecast.Convert[uint32](blockEndorsement.EndorserIndex) if err != nil { return a, nil, a.Errorf(errors.Wrapf(err, "failed to convert endorser index to uint32")) } - gi, err := a.baseInfo.storage.FindGenerator(state.ByIndex(generatorIndex)) + gi, err := a.baseInfo.storage.FindGenerator(h, state.ByIndex(generatorIndex)) if err != nil { return a, nil, a.Errorf(errors.Wrapf(err, "failed to find generator by index")) } @@ -334,8 +371,8 @@ func (a *NGState) BlockEndorsement(blockEndorsement *proto.BlockEndorsement) (St return a, nil, a.Errorf(errors.Wrapf(err, "failed to get last finalized block header for endorser address")) } // TODO check if generator is in the generator set. - endorserPK := gi.BLSPublicKey() - balance := gi.GenerationBalance() + endorserPK := gi.BLSPublicKey + balance := gi.Balance added, addErr := a.baseInfo.endorsements.Add(blockEndorsement, endorserPK, localFinalizedHeight, localFinalizedBlockHeader.BlockID(), balance, top.Parent) if addErr != nil { @@ -455,69 +492,48 @@ func (a *NGState) MinedBlock( return a, tasks.Tasks(tasks.NewMineMicroTask(0, block, limits, keyPair, vrf)), nil } -func (a *NGState) Endorse(parentBlockID proto.BlockID, +func (a *NGState) Endorse(height proto.Height, parentBlockID proto.BlockID, endorser state.GeneratorInfo, endorserSK bls.SecretKey) error { - endorserIndex := endorser.Index() - lastFinalizedHeight, err := a.baseInfo.storage.LastFinalizedHeight() - if err != nil { - return a.Errorf(errors.Wrap(err, "failed to get last finalized block height")) - } - lfh, err := safecast.Convert[uint32](lastFinalizedHeight) + msg, err := a.baseInfo.storage.BuildLocalEndorsementMessage(height, parentBlockID) if err != nil { - return a.Errorf(errors.Wrap(err, "failed to convert last finalized block height")) + return a.Errorf(errors.Wrap(err, "failed to build local endorsement message")) } - lastFinalizedBlock, err := a.baseInfo.storage.BlockByHeight(lastFinalizedHeight) - if err != nil { - return a.Errorf(errors.Wrap(err, "failed to get last finalized block")) - } - msg := proto.NewEndorsementCryptoMessage(lastFinalizedBlock.BlockID(), parentBlockID, lfh) cmb, err := msg.Bytes() if err != nil { return a.Errorf(errors.Wrap(err, "failed to create endorsement message")) } - slog.Debug("formed endorsement message", - "lastFinalizedBlockID", lastFinalizedBlock.BlockID(), - "lastFinalizedHeight", lastFinalizedHeight, - "EndorsedBlockID", parentBlockID, - "EndorserIndex", endorserIndex) signature, err := bls.Sign(endorserSK, cmb) if err != nil { return a.Errorf(errors.Wrap(err, "failed to sign block endorsement")) } - - finalizedHeight32, cErr := safecast.Convert[uint32](lastFinalizedHeight) - if cErr != nil { - return a.Errorf(errors.Wrapf(cErr, "lastFinalizedHeight overflows uint32: %v", lastFinalizedHeight)) - } endorseParentBlock := &proto.BlockEndorsement{ - EndorserIndex: endorserIndex, - FinalizedBlockID: lastFinalizedBlock.BlockID(), - FinalizedBlockHeight: finalizedHeight32, - EndorsedBlockID: parentBlockID, + EndorserIndex: endorser.Index, + FinalizedBlockID: msg.FinalizedBlockID, + FinalizedBlockHeight: msg.FinalizedBlockHeight, + EndorsedBlockID: msg.EndorsedBlockID, Signature: signature, } id, idErr := endorsementID(endorseParentBlock) if idErr != nil { return a.Errorf(errors.Wrap(idErr, "failed to compute endorsement id")) } - return a.addAndBroadcastOwnEndorsement(endorseParentBlock, endorser, lastFinalizedHeight, - lastFinalizedBlock.BlockID(), id) + slog.Debug("Formed endorsement message", slog.String("endorsementID", id.String()), + slog.Any("endorserIndex", endorser.Index), slog.String("signature", signature.String())) + return a.addAndBroadcastOwnEndorsement(endorseParentBlock, endorser, id) } func (a *NGState) addAndBroadcastOwnEndorsement( parentBlockEndorsement *proto.BlockEndorsement, endorser state.GeneratorInfo, - lastFinalizedHeight proto.Height, - lastFinalizedBlockID proto.BlockID, id crypto.Digest, ) error { top := a.baseInfo.storage.TopBlock() added, addErr := a.baseInfo.endorsements.Add( parentBlockEndorsement, - endorser.BLSPublicKey(), - lastFinalizedHeight, - lastFinalizedBlockID, - endorser.GenerationBalance(), + endorser.BLSPublicKey, + proto.Height(parentBlockEndorsement.FinalizedBlockHeight), + parentBlockEndorsement.FinalizedBlockID, + endorser.Balance, top.Parent, ) if addErr != nil { diff --git a/pkg/node/fsm/sync_state.go b/pkg/node/fsm/sync_state.go index 0a03707475..330ae61717 100644 --- a/pkg/node/fsm/sync_state.go +++ b/pkg/node/fsm/sync_state.go @@ -16,23 +16,23 @@ import ( "github.com/wavesplatform/gowaves/pkg/p2p/peer" "github.com/wavesplatform/gowaves/pkg/p2p/peer/extension" "github.com/wavesplatform/gowaves/pkg/proto" + "github.com/wavesplatform/gowaves/pkg/settings" "github.com/wavesplatform/gowaves/pkg/state" "github.com/wavesplatform/gowaves/pkg/types" ) const defaultMicroblockInterval = 5 * time.Second +// conf holds time parameters to calculate sync timeout. +// If nothing happens for more than timeout duration, it means that synchronization is stalled, +// so we should go to idle state and start again. type conf struct { - peerSyncWith peer.Peer - // if nothing happens more than N duration, means we stalled, so go to idle and again lastReceiveTime time.Time - - timeout time.Duration + timeout time.Duration } func (c conf) Now(tm types.Time) conf { return conf{ - peerSyncWith: c.peerSyncWith, lastReceiveTime: tm.Now(), timeout: c.timeout, } @@ -102,8 +102,14 @@ func (a *SyncState) Task(task tasks.AsyncTask) (State, Async, error) { a.baseInfo.logger.Debug("Checking timeout", "state", a.String()) timeout := a.conf.lastReceiveTime.Add(a.conf.timeout).Before(a.baseInfo.tm.Now()) if timeout { - a.baseInfo.logger.Debug("Synchronization with peer timed out", "state", a.String(), - "timeout", a.conf.timeout.String(), "peer", a.conf.peerSyncWith.ID()) + if a.baseInfo.syncPeer != nil { + a.baseInfo.logger.Debug("Synchronization with peer timed out", "state", a.String(), + "timeout", a.conf.timeout.String(), "peer", a.baseInfo.syncPeer.GetPeer().ID()) + a.baseInfo.logger.Debug("Suspending peer because no timely response was received", + slog.String("state", a.String()), + slog.String("peer", a.baseInfo.syncPeer.GetPeer().ID().String())) + a.baseInfo.peers.AddToBlackList(a.baseInfo.syncPeer.GetPeer(), time.Now(), "timeout during synchronization") + } return newIdleState(a.baseInfo), nil, a.Errorf(TimeoutErr) } return a, nil, nil @@ -123,12 +129,17 @@ func (a *SyncState) BlockIDs(peer peer.Peer, signatures []proto.BlockID) (State, if len(signatures) == 0 { a.baseInfo.logger.Debug("Empty IDs received from peer", "state", a.String(), "peer", peer.ID().String()) - return a, nil, nil + a.baseInfo.logger.Debug("Suspending peer because of empty blocks IDs received from peer", + slog.String("state", a.String()), + slog.String("peer", a.baseInfo.syncPeer.GetPeer().ID().String())) + a.baseInfo.peers.AddToBlackList(a.baseInfo.syncPeer.GetPeer(), time.Now(), "empty blocks IDs received from peer") + + return newIdleState(a.baseInfo), nil, nil } a.baseInfo.logger.Debug("Block IDs received from peer", "state", a.String(), "from", signatures[0].ShortString(), "to", signatures[len(signatures)-1].ShortString(), "peer", peer.ID().String()) - if !peer.Equal(a.conf.peerSyncWith) { + if !peer.Equal(a.baseInfo.syncPeer.GetPeer()) { a.baseInfo.logger.Debug("Block IDs received from incorrect peer", "state", a.String(), "peer", peer.ID().String(), "expectedPeer", a.baseInfo.syncPeer.GetPeer().ID().String()) return a, nil, nil @@ -166,7 +177,7 @@ func (a *SyncState) Score(p peer.Peer, score *proto.Score) (State, Async, error) } func (a *SyncState) Block(p peer.Peer, block *proto.Block) (State, Async, error) { - if !p.Equal(a.conf.peerSyncWith) { + if !p.Equal(a.baseInfo.syncPeer.GetPeer()) { return a, nil, nil } metrics.BlockReceivedFromExtension(block, p.Handshake().NodeName) @@ -185,7 +196,7 @@ func (a *SyncState) BlockSnapshot( blockID proto.BlockID, snapshot proto.BlockSnapshot, ) (State, Async, error) { - if !p.Equal(a.conf.peerSyncWith) { + if !p.Equal(a.baseInfo.syncPeer.GetPeer()) { return a, nil, nil } a.baseInfo.logger.Debug("Received snapshot for block", "state", a.String(), "peer", p.ID(), @@ -200,22 +211,26 @@ func (a *SyncState) BlockSnapshot( func (a *SyncState) MinedBlock( block *proto.Block, limits proto.MiningLimits, keyPair proto.KeyPair, vrf []byte, ) (State, Async, error) { - height, heightErr := a.baseInfo.storage.Height() - if heightErr != nil { - return a, nil, a.Errorf(heightErr) + height, err := a.baseInfo.storage.Height() + if err != nil { + return a, nil, a.Errorf(err) + } + ngActivated, err := a.baseInfo.storage.IsActiveAtHeight(int16(settings.NG), height) + if err != nil { + return a, nil, a.Errorf(err) + } + if ngActivated { + slog.Debug("Skipping mined block in Sync state because NG is already active", "state", a.String()) + return a, nil, nil } metrics.BlockMined(block) a.baseInfo.logger.Info("New block mined", "state", a.String(), "blockID", block.ID.String()) - _, err := a.baseInfo.blocksApplier.Apply( - a.baseInfo.storage, - []*proto.Block{block}, - ) - if err != nil { - slog.Warn("Failed to apply mined block", slog.String("state", a.String()), logging.Error(err)) + if _, apErr := a.baseInfo.blocksApplier.Apply(a.baseInfo.storage, []*proto.Block{block}); apErr != nil { + slog.Warn("Failed to apply mined block", slog.String("state", a.String()), logging.Error(apErr)) return a, nil, nil // We've failed to apply mined block, it's not an error } - metrics.BlockAppliedFromExtension(block, height+1) + metrics.BlockApplied(block, height+1) a.baseInfo.scheduler.Reschedule() // first we should send block @@ -275,11 +290,11 @@ func (a *SyncState) applyBlocksWithSnapshots( }) } if err != nil { - if errs.IsValidationError(err) || errs.IsValidationError(errors.Cause(err)) { + if a.baseInfo.syncPeer != nil && (errs.IsValidationError(err) || errs.IsValidationError(errors.Cause(err))) { a.baseInfo.logger.Debug("Suspending peer because of blocks application error", slog.String("state", a.String()), slog.String("peer", a.baseInfo.syncPeer.GetPeer().ID().String()), logging.Error(err)) - a.baseInfo.peers.Suspend(conf.peerSyncWith, time.Now(), err.Error()) + a.baseInfo.peers.AddToBlackList(a.baseInfo.syncPeer.GetPeer(), time.Now(), err.Error()) } for _, b := range blocks { metrics.BlockDeclinedFromExtension(b) @@ -309,7 +324,8 @@ func (a *SyncState) applyBlocksWithSnapshots( a.baseInfo.logger.Debug("Changing sync peer", "state", a.String(), "peer", np.ID().String()) return syncWithNewPeer(a, a.baseInfo, np) } - a.internal.AskBlocksIDs(extension.NewPeerExtension(a.conf.peerSyncWith, a.baseInfo.scheme, a.baseInfo.netLogger)) + a.internal.AskBlocksIDs(extension.NewPeerExtension(a.baseInfo.syncPeer.GetPeer(), a.baseInfo.scheme, + a.baseInfo.netLogger)) return newSyncState(baseInfo, conf, internal), nil, nil } diff --git a/pkg/node/network/network.go b/pkg/node/network/network.go index 4c7cce55d7..1b1729993f 100644 --- a/pkg/node/network/network.go +++ b/pkg/node/network/network.go @@ -114,8 +114,7 @@ func (n *Network) Run(ctx context.Context) { } func (n *Network) handleConnected(msg *peer.Connected) { - err := n.peers.NewConnection(msg.Peer) - if err != nil { + if err := n.peers.NewConnection(msg.Peer); err != nil { n.logger.Debug("Failed to establish connection with peer", slog.Any("direction", msg.Peer.Direction()), slog.Any("peer", msg.Peer.ID()), logging.Error(err)) return diff --git a/pkg/node/peers/mock_peer_manager.go b/pkg/node/peers/mock_peer_manager.go index 9c209b48c1..429ac9b740 100644 --- a/pkg/node/peers/mock_peer_manager.go +++ b/pkg/node/peers/mock_peer_manager.go @@ -868,104 +868,6 @@ func (_c *MockPeerManager_Spawned_Call) RunAndReturn(run func() []proto.IpPort) return _c } -// Suspend provides a mock function for the type MockPeerManager -func (_mock *MockPeerManager) Suspend(peer1 peer.Peer, suspendTime time.Time, reason string) { - _mock.Called(peer1, suspendTime, reason) - return -} - -// MockPeerManager_Suspend_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Suspend' -type MockPeerManager_Suspend_Call struct { - *mock.Call -} - -// Suspend is a helper method to define mock.On call -// - peer1 peer.Peer -// - suspendTime time.Time -// - reason string -func (_e *MockPeerManager_Expecter) Suspend(peer1 interface{}, suspendTime interface{}, reason interface{}) *MockPeerManager_Suspend_Call { - return &MockPeerManager_Suspend_Call{Call: _e.mock.On("Suspend", peer1, suspendTime, reason)} -} - -func (_c *MockPeerManager_Suspend_Call) Run(run func(peer1 peer.Peer, suspendTime time.Time, reason string)) *MockPeerManager_Suspend_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 peer.Peer - if args[0] != nil { - arg0 = args[0].(peer.Peer) - } - var arg1 time.Time - if args[1] != nil { - arg1 = args[1].(time.Time) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *MockPeerManager_Suspend_Call) Return() *MockPeerManager_Suspend_Call { - _c.Call.Return() - return _c -} - -func (_c *MockPeerManager_Suspend_Call) RunAndReturn(run func(peer1 peer.Peer, suspendTime time.Time, reason string)) *MockPeerManager_Suspend_Call { - _c.Run(run) - return _c -} - -// Suspended provides a mock function for the type MockPeerManager -func (_mock *MockPeerManager) Suspended() []storage.SuspendedPeer { - ret := _mock.Called() - - if len(ret) == 0 { - panic("no return value specified for Suspended") - } - - var r0 []storage.SuspendedPeer - if returnFunc, ok := ret.Get(0).(func() []storage.SuspendedPeer); ok { - r0 = returnFunc() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]storage.SuspendedPeer) - } - } - return r0 -} - -// MockPeerManager_Suspended_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Suspended' -type MockPeerManager_Suspended_Call struct { - *mock.Call -} - -// Suspended is a helper method to define mock.On call -func (_e *MockPeerManager_Expecter) Suspended() *MockPeerManager_Suspended_Call { - return &MockPeerManager_Suspended_Call{Call: _e.mock.On("Suspended")} -} - -func (_c *MockPeerManager_Suspended_Call) Run(run func()) *MockPeerManager_Suspended_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockPeerManager_Suspended_Call) Return(vs []storage.SuspendedPeer) *MockPeerManager_Suspended_Call { - _c.Call.Return(vs) - return _c -} - -func (_c *MockPeerManager_Suspended_Call) RunAndReturn(run func() []storage.SuspendedPeer) *MockPeerManager_Suspended_Call { - _c.Call.Return(run) - return _c -} - // UpdateKnownPeers provides a mock function for the type MockPeerManager func (_mock *MockPeerManager) UpdateKnownPeers(knownPeers []storage.KnownPeer) error { ret := _mock.Called(knownPeers) diff --git a/pkg/node/peers/peer_manager.go b/pkg/node/peers/peer_manager.go index 0cc576b805..03f6e74a19 100644 --- a/pkg/node/peers/peer_manager.go +++ b/pkg/node/peers/peer_manager.go @@ -2,7 +2,6 @@ package peers import ( "context" - "fmt" "log/slog" "math/big" "net" @@ -18,7 +17,7 @@ import ( ) const ( - suspendDuration = 5 * time.Minute + restrictionDuration = 5 * time.Minute clearRestrictedPeersInterval = 1 * time.Minute ) @@ -49,8 +48,6 @@ type PeerManager interface { NewConnection(peer.Peer) error ConnectedCount() int EachConnected(func(peer.Peer, *proto.Score)) - Suspend(peer peer.Peer, suspendTime time.Time, reason string) - Suspended() []storage.SuspendedPeer AddToBlackList(peer peer.Peer, blockTime time.Time, reason string) BlackList() []storage.BlackListedPeer ClearBlackList() error @@ -114,11 +111,7 @@ func (a *PeerManagerImpl) NewConnection(p peer.Peer) error { } now := time.Now() - if p.Direction() == peer.Outgoing && a.suspended(p, now) { - _ = p.Close() - return errors.Wrapf(ErrPeerSuspended, "%s", p.ID()) - } - if p.Direction() == peer.Incoming && a.blackListed(p, now) { + if a.blackListed(p, now) { _ = p.Close() return errors.Wrapf(ErrPeerBlackListed, "%s", p.ID()) } @@ -181,28 +174,6 @@ func (a *PeerManagerImpl) EachConnected(f func(peer peer.Peer, score *big.Int)) ) } -func (a *PeerManagerImpl) Suspend(p peer.Peer, suspendTime time.Time, reason string) { - a.Disconnect(p) - a.mu.Lock() - defer a.mu.Unlock() - suspended := storage.NewSuspendedPeer( - storage.IpFromIpPort(p.RemoteAddr().ToIpPort()), - suspendTime.UnixMilli(), - suspendDuration, - reason, - ) - if err := a.peerStorage.AddSuspended([]storage.SuspendedPeer{suspended}); err != nil { - slog.Error("Failed to suspend peer", slog.Any("peer", p.ID()), slog.String("reason", reason), - logging.Error(err)) - } else { - a.logger.Debug("Suspending peer", "peer", p.ID(), "reason", reason) - } -} - -func (a *PeerManagerImpl) Suspended() []storage.SuspendedPeer { - return a.peerStorage.Suspended(time.Now()) -} - func (a *PeerManagerImpl) AddToBlackList(p peer.Peer, blockTime time.Time, reason string) { if a.blackListDuration <= 0 { return @@ -314,7 +285,7 @@ func (a *PeerManagerImpl) SpawnOutgoingConnections(ctx context.Context) { if _, ok := a.spawned[ipPort]; ok { continue } - if a.peerStorage.IsSuspendedIP(knowPeer.IP(), time.Now()) { + if a.peerStorage.IsBlackListedIP(knowPeer.IP(), time.Now()) { continue } @@ -372,6 +343,9 @@ func (a *PeerManagerImpl) Connect(ctx context.Context, addr proto.TCPAddr) error if _, ok := a.spawned[addr.ToIpPort()]; ok { return nil } + if a.peerStorage.IsBlackListedIP(storage.IpFromIpPort(addr.ToIpPort()), time.Now()) { + return nil + } a.spawned[addr.ToIpPort()] = struct{}{} @@ -443,12 +417,18 @@ func (a *PeerManagerImpl) CheckPeerWithMaxScore(p peer.Peer) (peer.Peer, bool) { a.mu.RLock() defer a.mu.RUnlock() - var pid peer.ID - pIDStr := "n/a" - if p != nil { - pid = p.ID() - pIDStr = p.ID().String() + if p == nil { // Fast path for the case when we don't have current peer, just return the peer with max score. + npi, ok := a.active.getPeerWithMaxScore() + if !ok { // No active peers, no peer with max score. + return nil, false + } + a.logger.Debug("Selecting peer with maximum score", "peer", npi.peer.ID().String()) + return npi.peer, false } + + // Normal peer selection. + pid := p.ID() + pIDStr := p.ID().String() cpi, ok := a.active.get(pid) if !ok { return nil, false @@ -500,9 +480,6 @@ func (a *PeerManagerImpl) unsafeConnectedCount() int { func (a *PeerManagerImpl) clearRestrictedPeers(now time.Time) { a.mu.Lock() defer a.mu.Unlock() - if err := a.peerStorage.RefreshSuspended(now); err != nil { - slog.Error("Failed to clear suspended peers", logging.Error(err)) - } if err := a.peerStorage.RefreshBlackList(now); err != nil { slog.Error("Failed to clear black listed peers", logging.Error(err)) } @@ -515,13 +492,6 @@ func (a *PeerManagerImpl) addConnected(peer peer.Peer) { a.active.add(peer) } -func (a *PeerManagerImpl) suspended(p peer.Peer, now time.Time) bool { - a.mu.RLock() - defer a.mu.RUnlock() - ip := storage.IpFromIpPort(p.RemoteAddr().ToIpPort()) - return a.peerStorage.IsSuspendedIP(ip, now) -} - func (a *PeerManagerImpl) blackListed(p peer.Peer, now time.Time) bool { a.mu.RLock() defer a.mu.RUnlock() @@ -530,14 +500,7 @@ func (a *PeerManagerImpl) blackListed(p peer.Peer, now time.Time) bool { } func (a *PeerManagerImpl) restrict(p peer.Peer, now time.Time, reason string) { - switch d := p.Direction(); d { - case peer.Incoming: - a.AddToBlackList(p, now, reason) - case peer.Outgoing: - a.Suspend(p, now, reason) - default: - panic(fmt.Sprintf("BUG, CREATE REPORT: can't restrict peer because of unexpected peer direction (%d)", d)) - } + a.AddToBlackList(p, now, reason) } // countDirections counts connected peers by its directions and returns number of inbound and outbound connections. diff --git a/pkg/node/peers/peer_storage_internal_test.go b/pkg/node/peers/peer_storage_internal_test.go index 4267a5d0a4..0619eaae4b 100644 --- a/pkg/node/peers/peer_storage_internal_test.go +++ b/pkg/node/peers/peer_storage_internal_test.go @@ -10,7 +10,7 @@ import ( "github.com/wavesplatform/gowaves/pkg/proto" ) -func TestPeerManagerImpl_Suspend(t *testing.T) { +func TestPeerManagerImpl_BlackList(t *testing.T) { now := time.Now() tcpAddr := proto.NewTCPAddrFromString("32.34.46.1:4535") reason := "some-reason" @@ -22,17 +22,18 @@ func TestPeerManagerImpl_Suspend(t *testing.T) { p.EXPECT().ID().Return(nil) peerStorage := NewMockPeerStorage(t) - peerStorage.EXPECT().AddSuspended([]storage.SuspendedPeer{{ + peerStorage.EXPECT().AddToBlackList([]storage.BlackListedPeer{{ IP: storage.IpFromIpPort(tcpAddr.ToIpPort()), RestrictTimestampMillis: now.UnixMilli(), - RestrictDuration: suspendDuration, + RestrictDuration: restrictionDuration, Reason: reason, }}).Return(nil) manager := PeerManagerImpl{ - peerStorage: peerStorage, - logger: slog.New(slog.DiscardHandler), + peerStorage: peerStorage, + logger: slog.New(slog.DiscardHandler), + blackListDuration: restrictionDuration, } - manager.Suspend(p, now, reason) + manager.AddToBlackList(p, now, reason) } diff --git a/pkg/proto/finalization.go b/pkg/proto/finalization.go index 441331ff92..a2969dcc88 100644 --- a/pkg/proto/finalization.go +++ b/pkg/proto/finalization.go @@ -58,8 +58,8 @@ func (msg *EndorsementCryptoMessage) Bytes() ([]byte, error) { // BlockEndorsement represents an endorsement of a block by a validator. type BlockEndorsement struct { EndorserIndex uint32 `json:"endorserIndex"` - FinalizedBlockID BlockID `json:"finalizedBlockID"` - FinalizedBlockHeight uint32 `json:"finalizedBlockHeight"` + FinalizedBlockID BlockID `json:"finalizedBlockId"` + FinalizedBlockHeight uint32 `json:"finalizedHeight"` EndorsedBlockID BlockID `json:"endorsedBlockId"` Signature bls.Signature `json:"signature"` } @@ -110,14 +110,30 @@ func (e *BlockEndorsement) ToProtobuf() (*g.EndorseBlock, error) { } type FinalizationVoting struct { - EndorserIndexes []uint32 `json:"endorserIndexes"` - FinalizedBlockHeight Height `json:"finalizedBlockHeight"` + EndorserIndexes []uint32 `json:"endorserIndexes,omitempty"` + FinalizedBlockHeight Height `json:"finalizedHeight"` AggregatedEndorsementSignature bls.Signature `json:"aggregatedEndorsementSignature"` - ConflictEndorsements []BlockEndorsement `json:"conflictEndorsements"` + ConflictEndorsements []BlockEndorsement `json:"conflictEndorsements,omitempty"` +} + +func (f *FinalizationVoting) IsEmpty() bool { + return len(f.EndorserIndexes) == 0 && f.FinalizedBlockHeight == 0 && len(f.ConflictEndorsements) == 0 && + (f.AggregatedEndorsementSignature == bls.Signature{}) } // Validate checks that FinalizationVoting doesn't have any duplicate endorsers indexes. func (f *FinalizationVoting) Validate() error { + if f.IsEmpty() { // Empty structure nothing to check. + return nil + } + const genesisBlockHeight = 1 + if f.FinalizedBlockHeight < genesisBlockHeight { + return fmt.Errorf("invalid finalization voting: finalized block height %d is less than genesis block height %d", + f.FinalizedBlockHeight, genesisBlockHeight) + } + if len(f.EndorserIndexes) == 0 && len(f.ConflictEndorsements) == 0 { + return fmt.Errorf("invalid finalization voting: both endorsers and conflict endorsements are empty") + } indexes := make(map[uint32]struct{}, len(f.ConflictEndorsements)+len(f.EndorserIndexes)) for _, ce := range f.ConflictEndorsements { if _, seen := indexes[ce.EndorserIndex]; seen { @@ -137,6 +153,19 @@ func (f *FinalizationVoting) Validate() error { return nil } +// CheckSizes validates the sizes of finalization fields against the size of generator set. +// The number of endorsements and conflicting endorsements must not exceed the generator set size. +func (f *FinalizationVoting) CheckSizes(generatorSetSize int) error { + if ces := len(f.ConflictEndorsements); ces > generatorSetSize { + return fmt.Errorf("conflicting endorsements count %d exceeds generator set size %d", + ces, generatorSetSize) + } + if eis := len(f.EndorserIndexes); eis > generatorSetSize { + return fmt.Errorf("endorsements count %d exceeds generator set size %d", eis, generatorSetSize) + } + return nil +} + func (f *FinalizationVoting) Marshal() ([]byte, error) { endBlockProto, err := f.ToProtobuf() if err != nil { diff --git a/pkg/proto/finalization_test.go b/pkg/proto/finalization_test.go index d15fb27f5c..df76af5f9e 100644 --- a/pkg/proto/finalization_test.go +++ b/pkg/proto/finalization_test.go @@ -117,7 +117,8 @@ func TestFinalizationVotingValidation(t *testing.T) { fail bool err string }{ - {indexes: nil, conflicts: nil, fail: false}, + {indexes: nil, conflicts: nil, fail: true, + err: "invalid finalization voting: both endorsers and conflict endorsements are empty"}, {indexes: nil, conflicts: []uint32{2, 0, 1}, fail: false}, {indexes: nil, conflicts: []uint32{2, 1, 2}, fail: true, err: "invalid finalization voting: duplicate conflicting endorsement with endorser index 2"}, diff --git a/pkg/ride/functions.gen.go b/pkg/ride/functions.gen.go index 1330d187ac..286147fa8c 100644 --- a/pkg/ride/functions.gen.go +++ b/pkg/ride/functions.gen.go @@ -444,12 +444,12 @@ func init() { _functions_map_V9 = map[string]rideFunction{"!": unaryNot, "!=": neq, "-": unaryMinus, "0": eq, "1": instanceOf, "100": sum, "1001": transactionHeightByID, "1004": assetInfoV4, "1005": blockInfoByHeight, "1006": transferByID, "1007": wavesBalanceV4, "1008": assetBalanceV4, "1009": hashScriptAtAddress, "101": sub, "102": gt, "1020": invoke, "1021": reentrantInvoke, "103": ge, "104": mul, "1040": intFromArray, "1041": booleanFromArray, "1042": bytesFromArray, "1043": stringFromArray, "105": div, "1050": intFromState, "1051": booleanFromState, "1052": bytesFromState, "1053": stringFromState, "1054": isDataStorageUntouched, "1055": intFromSelfState, "1056": booleanFromSelfState, "1057": bytesFromSelfState, "1058": stringFromSelfState, "106": mod, "1060": addressFromRecipient, "1061": addressToString, "1062": addressFromString, "1063": addressFromPublicKeyStrict, "107": fraction, "1070": transferFromProtobuf, "108": pow, "1080": calculateAssetID, "1081": calculateLeaseID, "109": log, "1090": simplifiedIssue, "1091": fullIssue, "1092": simplifiedLease, "1093": fullLease, "110": fractionIntRounds, "1100": limitedCreateList, "1101": appendToList, "1102": concatList, "1103": indexOfList, "1104": lastIndexOfList, "1105": listRemoveByIndex, "1106": listReplaceByIndex, "1107": fillList, "118": powBigInt, "119": logBigInt, "1200": bytesToUTF8StringV4, "1201": bytesToInt, "1202": bytesToIntWithOffset, "1203": indexOfSubstring, "1204": indexOfSubstringWithOffset, "1205": splitStringV6, "1206": parseInt, "1207": lastIndexOfSubstring, "1208": lastIndexOfSubstringWithOffset, "1209": makeStringV6, "1210": makeString2C, "1211": makeString11C, "1212": splitString4C, "1213": splitString51C, "1214": replaceFirst, "1215": replaceAll, "1300": newTuple2, "1301": newTuple3, "1302": newTuple4, "1303": newTuple5, "1304": newTuple6, "1305": newTuple7, "1306": newTuple8, "1307": newTuple9, "1308": newTuple10, "1309": newTuple11, "1310": newTuple12, "1311": newTuple13, "1312": newTuple14, "1313": newTuple15, "1314": newTuple16, "1315": newTuple17, "1316": newTuple18, "1317": newTuple19, "1318": newTuple20, "1319": newTuple21, "1320": newTuple22, "1350": sizeTuple, "2": throw, "200": sizeBytes, "201": takeBytesV6, "202": dropBytesV6, "203": concatBytes, "204": takeRightBytesV6, "205": dropRightBytesV6, "2400": bls12Groth16Verify_1, "2401": bls12Groth16Verify_2, "2402": bls12Groth16Verify_3, "2403": bls12Groth16Verify_4, "2404": bls12Groth16Verify_5, "2405": bls12Groth16Verify_6, "2406": bls12Groth16Verify_7, "2407": bls12Groth16Verify_8, "2408": bls12Groth16Verify_9, "2409": bls12Groth16Verify_10, "2410": bls12Groth16Verify_11, "2411": bls12Groth16Verify_12, "2412": bls12Groth16Verify_13, "2413": bls12Groth16Verify_14, "2414": bls12Groth16Verify_15, "2450": bn256Groth16Verify_1, "2451": bn256Groth16Verify_2, "2452": bn256Groth16Verify_3, "2453": bn256Groth16Verify_4, "2454": bn256Groth16Verify_5, "2455": bn256Groth16Verify_6, "2456": bn256Groth16Verify_7, "2457": bn256Groth16Verify_8, "2458": bn256Groth16Verify_9, "2459": bn256Groth16Verify_10, "2460": bn256Groth16Verify_11, "2461": bn256Groth16Verify_12, "2462": bn256Groth16Verify_13, "2463": bn256Groth16Verify_14, "2464": bn256Groth16Verify_15, "2500": sigVerify_8, "2501": sigVerify_16, "2502": sigVerify_32, "2503": sigVerify_64, "2504": sigVerify_128, "2600": rsaVerify_16, "2601": rsaVerify_32, "2602": rsaVerify_64, "2603": rsaVerify_128, "2700": keccak256_16, "2701": keccak256_32, "2702": keccak256_64, "2703": keccak256_128, "2800": blake2b256_16, "2801": blake2b256_32, "2802": blake2b256_64, "2803": blake2b256_128, "2900": sha256_16, "2901": sha256_32, "2902": sha256_64, "2903": sha256_128, "3": getType, "300": concatStrings, "303": takeStringV6, "304": dropStringV6, "305": sizeString, "306": takeRightStringV6, "307": dropRightStringV6, "310": toBigInt, "311": sumBigInt, "312": subtractBigInt, "313": multiplyBigInt, "314": divideBigInt, "315": moduloBigInt, "316": fractionBigInt, "317": fractionBigIntRounds, "318": unaryMinusBigInt, "319": gtBigInt, "320": geBigInt, "400": sizeList, "401": getList, "405": median, "406": listMax, "407": listMin, "408": maxListBigInt, "409": minListBigInt, "410": intToBytes, "411": stringToBytes, "412": booleanToBytes, "413": bigIntToBytes, "414": bytesToBigInt, "415": bytesToBigIntLim, "416": bigIntToInt, "420": intToString, "421": booleanToString, "422": bigIntToString, "423": stringToBigInt, "424": stringToBigIntOpt, "425": medianListBigInt, "500": sigVerify, "501": keccak256, "502": blake2b256, "503": sha256, "504": rsaVerify, "600": toBase58V4, "601": fromBase58, "602": toBase64V4, "603": fromBase64, "604": toBase16V4, "605": fromBase16V4, "606": toBase641C, "607": fromBase641C, "608": toBase161C, "609": fromBase161C, "701": rebuildMerkleRoot, "800": bls12Groth16Verify, "801": bn256Groth16Verify, "900": ecRecover, "901": calculateDelay, "902": validateCertChain, "903": secP256Verify, "@extrNative(1040)": intValueFromArray, "@extrNative(1041)": booleanValueFromArray, "@extrNative(1042)": bytesValueFromArray, "@extrNative(1043)": stringValueFromArray, "@extrNative(1050)": intValueFromState, "@extrNative(1051)": booleanValueFromState, "@extrNative(1052)": bytesValueFromState, "@extrNative(1053)": stringValueFromState, "@extrNative(1055)": intValueFromSelfState, "@extrNative(1056)": booleanValueFromSelfState, "@extrNative(1057)": bytesValueFromSelfState, "@extrNative(1058)": stringValueFromSelfState, "@extrNative(1062)": addressValueFromString, "@extrUser(addressFromString)": addressValueFromString, "@extrUser(getBinary)": bytesValueFromArrayByIndex, "@extrUser(getBoolean)": booleanValueFromArrayByIndex, "@extrUser(getInteger)": intValueFromArrayByIndex, "@extrUser(getString)": stringValueFromArrayByIndex, "Address": address, "Alias": alias, "Asset": assetV4Constructor, "AssetPair": assetPairConstructor, "AttachedPayment": attachedPaymentConstructor, "BalanceDetails": balanceDetailsConstructor, "BinaryEntry": binaryEntryConstructor, "BlockInfo": blockInfoV7Constructor, "BooleanEntry": booleanEntryConstructor, "Burn": burnConstructor, "BurnTransaction": burnTransactionConstructor, "Buy": createBuy, "Ceiling": createCeiling, "CommitToGenerationTransaction": commitToGenerationTransactionConstructor, "CreateAliasTransaction": createAliasTransactionConstructor, "DataTransaction": dataTransactionConstructor, "DeleteEntry": deleteEntryConstructor, "Down": createDown, "ExchangeTransaction": exchangeTransactionConstructor, "Floor": createFloor, "GenesisTransaction": genesisTransactionConstructor, "HalfDown": createHalfDown, "HalfEven": createHalfEven, "HalfUp": createHalfUp, "IntegerEntry": integerEntryConstructor, "Invocation": invocationV5Constructor, "InvokeExpressionTransaction": invokeExpressionTransactionConstructor, "InvokeScriptTransaction": invokeScriptTransactionV4Constructor, "Issue": issueConstructor, "IssueTransaction": issueTransactionConstructor, "Lease": leaseConstructor, "LeaseCancel": leaseCancelConstructor, "LeaseCancelTransaction": leaseCancelTransactionConstructor, "LeaseTransaction": leaseTransactionConstructor, "MassTransferTransaction": massTransferTransactionConstructor, "Md5": createMd5, "NoAlg": createNoAlg, "Order": orderV8Constructor, "PaymentTransaction": paymentTransactionConstructor, "Reissue": reissueConstructor, "ReissueTransaction": reissueTransactionConstructor, "ScriptTransfer": scriptTransferConstructor, "Sell": createSell, "SetAssetScriptTransaction": setAssetScriptTransactionConstructor, "SetScriptTransaction": setScriptTransactionConstructor, "Sha1": createSha1, "Sha224": createSha224, "Sha256": createSha256, "Sha3224": createSha3224, "Sha3256": createSha3256, "Sha3384": createSha3384, "Sha3512": createSha3512, "Sha384": createSha384, "Sha512": createSha512, "SponsorFee": sponsorFeeConstructor, "SponsorFeeTransaction": sponsorFeeTransactionConstructor, "StringEntry": stringEntryConstructor, "Transfer": transferConstructor, "TransferTransaction": transferTransactionConstructor, "Unit": unit, "Up": createUp, "UpdateAssetInfoTransaction": updateAssetInfoTransactionConstructor, "addressFromPublicKey": addressFromPublicKey, "contains": contains, "containsElement": containsElement, "dropRight": dropRightString, "dropRightBytes": dropRightBytes, "getBinary": bytesFromArrayByIndex, "getBoolean": booleanFromArrayByIndex, "getInteger": intFromArrayByIndex, "getString": stringFromArrayByIndex, "isDefined": isDefined, "parseIntValue": parseIntValue, "sqrt": sqrt, "sqrtBigInt": sqrtBigInt, "takeRight": takeRightString, "takeRightBytes": takeRightBytes, "throw": throw0, "value": value, "valueOrElse": valueOrElse, "valueOrErrorMessage": valueOrErrorMessage} } -var _catalogue_V9 = [...]int{1, 1, 1, 1, 1, 1, 20, 15, 5, 60, 10, 10, 200, 1, 1, 75, 75, 1, 1, 10, 10, 10, 10, 1, 10, 10, 10, 10, 10, 10, 10, 10, 10, 1, 5, 1, 1, 1, 1, 5, 28, 10, 1, 100, 1, 1, 1, 1, 1, 1, 1, 4, 5, 5, 4, 4, 2, 270, 200, 7, 1, 1, 3, 3, 1, 2, 3, 3, 1, 2, 11, 4, 51, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 6, 2, 6, 6, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 2500, 2600, 800, 850, 950, 1000, 1050, 1100, 1150, 1200, 1250, 1300, 1350, 1400, 1450, 1550, 1600, 43, 50, 64, 93, 150, 500, 550, 625, 750, 20, 39, 74, 147, 13, 29, 58, 115, 5, 9, 17, 32, 1, 1, 20, 20, 1, 20, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 20, 3, 3, 6, 6, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 35, 180, 195, 136, 36, 1000, 3, 1, 35, 40, 10, 10, 1, 1, 1, 1, 30, 2700, 1650, 70, 1, 43, 43, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 1, 124, 10, 10, 10, 10, 1, 1, 10, 2, 2, 4, 2, 8, 2, 2, 10, 0, 0, 11, 9, 9, 1, 0, 14, 0, 6, 0, 0, 0, 2, 8, 10, 13, 7, 14, 3, 1, 9, 10, 13, 0, 0, 15, 10, 3, 11, 3, 0, 10, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 10, 2, 2, 13, 0, 0, 11, 63, 3, 5, 20, 6, 30, 30, 30, 30, 1, 2, 2, 5, 20, 6, 1, 2, 2, 2} +var _catalogue_V9 = [...]int{1, 1, 1, 1, 1, 1, 20, 15, 5, 60, 10, 10, 200, 1, 1, 75, 75, 1, 1, 10, 10, 10, 10, 1, 10, 10, 10, 10, 10, 10, 10, 10, 10, 1, 5, 1, 1, 1, 1, 5, 28, 10, 1, 100, 1, 1, 1, 1, 1, 1, 1, 4, 5, 5, 1, 1, 2, 270, 200, 7, 1, 1, 3, 3, 1, 2, 3, 3, 1, 2, 11, 4, 51, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 2500, 2600, 800, 850, 950, 1000, 1050, 1100, 1150, 1200, 1250, 1300, 1350, 1400, 1450, 1550, 1600, 43, 50, 64, 93, 150, 500, 550, 625, 750, 20, 39, 74, 147, 13, 29, 58, 115, 5, 9, 17, 32, 1, 1, 2, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 3, 3, 6, 6, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 35, 180, 195, 136, 36, 1000, 3, 1, 3, 12, 4, 4, 1, 1, 1, 1, 3, 2700, 1650, 70, 1, 43, 43, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 1, 124, 14, 14, 14, 14, 1, 1, 10, 2, 2, 4, 2, 8, 2, 2, 10, 0, 0, 11, 9, 9, 1, 0, 14, 0, 6, 0, 0, 0, 2, 8, 10, 13, 7, 14, 3, 1, 9, 10, 13, 0, 0, 15, 10, 3, 11, 3, 0, 10, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 10, 2, 2, 13, 0, 0, 11, 63, 3, 5, 20, 6, 7, 7, 7, 7, 1, 2, 2, 5, 20, 6, 1, 2, 2, 2} -var CatalogueV9 = map[string]int{"!": 1, "!=": 1, "-": 1, "0": 1, "1": 1, "100": 1, "1001": 20, "1004": 15, "1005": 5, "1006": 60, "1007": 10, "1008": 10, "1009": 200, "101": 1, "102": 1, "1020": 75, "1021": 75, "103": 1, "104": 1, "1040": 10, "1041": 10, "1042": 10, "1043": 10, "105": 1, "1050": 10, "1051": 10, "1052": 10, "1053": 10, "1054": 10, "1055": 10, "1056": 10, "1057": 10, "1058": 10, "106": 1, "1060": 5, "1061": 1, "1062": 1, "1063": 1, "107": 1, "1070": 5, "108": 28, "1080": 10, "1081": 1, "109": 100, "1090": 1, "1091": 1, "1092": 1, "1093": 1, "110": 1, "1100": 1, "1101": 1, "1102": 4, "1103": 5, "1104": 5, "1105": 4, "1106": 4, "1107": 2, "118": 270, "119": 200, "1200": 7, "1201": 1, "1202": 1, "1203": 3, "1204": 3, "1205": 1, "1206": 2, "1207": 3, "1208": 3, "1209": 1, "1210": 2, "1211": 11, "1212": 4, "1213": 51, "1214": 2, "1215": 2, "1300": 1, "1301": 1, "1302": 1, "1303": 1, "1304": 1, "1305": 1, "1306": 1, "1307": 1, "1308": 1, "1309": 1, "1310": 1, "1311": 1, "1312": 1, "1313": 1, "1314": 1, "1315": 1, "1316": 1, "1317": 1, "1318": 1, "1319": 1, "1320": 1, "1350": 1, "2": 1, "200": 1, "201": 6, "202": 6, "203": 2, "204": 6, "205": 6, "2400": 1200, "2401": 1300, "2402": 1400, "2403": 1500, "2404": 1600, "2405": 1700, "2406": 1800, "2407": 1900, "2408": 2000, "2409": 2100, "2410": 2200, "2411": 2300, "2412": 2400, "2413": 2500, "2414": 2600, "2450": 800, "2451": 850, "2452": 950, "2453": 1000, "2454": 1050, "2455": 1100, "2456": 1150, "2457": 1200, "2458": 1250, "2459": 1300, "2460": 1350, "2461": 1400, "2462": 1450, "2463": 1550, "2464": 1600, "2500": 43, "2501": 50, "2502": 64, "2503": 93, "2504": 150, "2600": 500, "2601": 550, "2602": 625, "2603": 750, "2700": 20, "2701": 39, "2702": 74, "2703": 147, "2800": 13, "2801": 29, "2802": 58, "2803": 115, "2900": 5, "2901": 9, "2902": 17, "2903": 32, "3": 1, "300": 1, "303": 20, "304": 20, "305": 1, "306": 20, "307": 20, "310": 1, "311": 1, "312": 1, "313": 1, "314": 1, "315": 1, "316": 1, "317": 1, "318": 1, "319": 1, "320": 1, "400": 2, "401": 2, "405": 20, "406": 3, "407": 3, "408": 6, "409": 6, "410": 1, "411": 8, "412": 1, "413": 1, "414": 1, "415": 1, "416": 1, "420": 1, "421": 1, "422": 1, "423": 1, "424": 1, "425": 35, "500": 180, "501": 195, "502": 136, "503": 36, "504": 1000, "600": 3, "601": 1, "602": 35, "603": 40, "604": 10, "605": 10, "606": 1, "607": 1, "608": 1, "609": 1, "701": 30, "800": 2700, "801": 1650, "900": 70, "901": 1, "902": 43, "903": 43, "@extrNative(1040)": 10, "@extrNative(1041)": 10, "@extrNative(1042)": 10, "@extrNative(1043)": 10, "@extrNative(1050)": 10, "@extrNative(1051)": 10, "@extrNative(1052)": 10, "@extrNative(1053)": 10, "@extrNative(1055)": 10, "@extrNative(1056)": 10, "@extrNative(1057)": 10, "@extrNative(1058)": 10, "@extrNative(1062)": 1, "@extrUser(addressFromString)": 124, "@extrUser(getBinary)": 10, "@extrUser(getBoolean)": 10, "@extrUser(getInteger)": 10, "@extrUser(getString)": 10, "Address": 1, "Alias": 1, "Asset": 10, "AssetPair": 2, "AttachedPayment": 2, "BalanceDetails": 4, "BinaryEntry": 2, "BlockInfo": 8, "BooleanEntry": 2, "Burn": 2, "BurnTransaction": 10, "Buy": 0, "Ceiling": 0, "CommitToGenerationTransaction": 11, "CreateAliasTransaction": 9, "DataTransaction": 9, "DeleteEntry": 1, "Down": 0, "ExchangeTransaction": 14, "Floor": 0, "GenesisTransaction": 6, "HalfDown": 0, "HalfEven": 0, "HalfUp": 0, "IntegerEntry": 2, "Invocation": 8, "InvokeExpressionTransaction": 10, "InvokeScriptTransaction": 13, "Issue": 7, "IssueTransaction": 14, "Lease": 3, "LeaseCancel": 1, "LeaseCancelTransaction": 9, "LeaseTransaction": 10, "MassTransferTransaction": 13, "Md5": 0, "NoAlg": 0, "Order": 15, "PaymentTransaction": 10, "Reissue": 3, "ReissueTransaction": 11, "ScriptTransfer": 3, "Sell": 0, "SetAssetScriptTransaction": 10, "SetScriptTransaction": 9, "Sha1": 0, "Sha224": 0, "Sha256": 0, "Sha3224": 0, "Sha3256": 0, "Sha3384": 0, "Sha3512": 0, "Sha384": 0, "Sha512": 0, "SponsorFee": 2, "SponsorFeeTransaction": 10, "StringEntry": 2, "Transfer": 2, "TransferTransaction": 13, "Unit": 0, "Up": 0, "UpdateAssetInfoTransaction": 11, "addressFromPublicKey": 63, "contains": 3, "containsElement": 5, "dropRight": 20, "dropRightBytes": 6, "getBinary": 30, "getBoolean": 30, "getInteger": 30, "getString": 30, "isDefined": 1, "parseIntValue": 2, "sqrt": 2, "sqrtBigInt": 5, "takeRight": 20, "takeRightBytes": 6, "throw": 1, "value": 2, "valueOrElse": 2, "valueOrErrorMessage": 2} +var CatalogueV9 = map[string]int{"!": 1, "!=": 1, "-": 1, "0": 1, "1": 1, "100": 1, "1001": 20, "1004": 15, "1005": 5, "1006": 60, "1007": 10, "1008": 10, "1009": 200, "101": 1, "102": 1, "1020": 75, "1021": 75, "103": 1, "104": 1, "1040": 10, "1041": 10, "1042": 10, "1043": 10, "105": 1, "1050": 10, "1051": 10, "1052": 10, "1053": 10, "1054": 10, "1055": 10, "1056": 10, "1057": 10, "1058": 10, "106": 1, "1060": 5, "1061": 1, "1062": 1, "1063": 1, "107": 1, "1070": 5, "108": 28, "1080": 10, "1081": 1, "109": 100, "1090": 1, "1091": 1, "1092": 1, "1093": 1, "110": 1, "1100": 1, "1101": 1, "1102": 4, "1103": 5, "1104": 5, "1105": 1, "1106": 1, "1107": 2, "118": 270, "119": 200, "1200": 7, "1201": 1, "1202": 1, "1203": 3, "1204": 3, "1205": 1, "1206": 2, "1207": 3, "1208": 3, "1209": 1, "1210": 2, "1211": 11, "1212": 4, "1213": 51, "1214": 2, "1215": 2, "1300": 1, "1301": 1, "1302": 1, "1303": 1, "1304": 1, "1305": 1, "1306": 1, "1307": 1, "1308": 1, "1309": 1, "1310": 1, "1311": 1, "1312": 1, "1313": 1, "1314": 1, "1315": 1, "1316": 1, "1317": 1, "1318": 1, "1319": 1, "1320": 1, "1350": 1, "2": 1, "200": 1, "201": 2, "202": 2, "203": 2, "204": 2, "205": 2, "2400": 1200, "2401": 1300, "2402": 1400, "2403": 1500, "2404": 1600, "2405": 1700, "2406": 1800, "2407": 1900, "2408": 2000, "2409": 2100, "2410": 2200, "2411": 2300, "2412": 2400, "2413": 2500, "2414": 2600, "2450": 800, "2451": 850, "2452": 950, "2453": 1000, "2454": 1050, "2455": 1100, "2456": 1150, "2457": 1200, "2458": 1250, "2459": 1300, "2460": 1350, "2461": 1400, "2462": 1450, "2463": 1550, "2464": 1600, "2500": 43, "2501": 50, "2502": 64, "2503": 93, "2504": 150, "2600": 500, "2601": 550, "2602": 625, "2603": 750, "2700": 20, "2701": 39, "2702": 74, "2703": 147, "2800": 13, "2801": 29, "2802": 58, "2803": 115, "2900": 5, "2901": 9, "2902": 17, "2903": 32, "3": 1, "300": 1, "303": 2, "304": 2, "305": 1, "306": 2, "307": 2, "310": 1, "311": 1, "312": 1, "313": 1, "314": 1, "315": 1, "316": 1, "317": 1, "318": 1, "319": 1, "320": 1, "400": 1, "401": 1, "405": 20, "406": 3, "407": 3, "408": 6, "409": 6, "410": 1, "411": 8, "412": 1, "413": 1, "414": 1, "415": 1, "416": 1, "420": 1, "421": 1, "422": 1, "423": 1, "424": 1, "425": 35, "500": 180, "501": 195, "502": 136, "503": 36, "504": 1000, "600": 3, "601": 1, "602": 3, "603": 12, "604": 4, "605": 4, "606": 1, "607": 1, "608": 1, "609": 1, "701": 3, "800": 2700, "801": 1650, "900": 70, "901": 1, "902": 43, "903": 43, "@extrNative(1040)": 10, "@extrNative(1041)": 10, "@extrNative(1042)": 10, "@extrNative(1043)": 10, "@extrNative(1050)": 10, "@extrNative(1051)": 10, "@extrNative(1052)": 10, "@extrNative(1053)": 10, "@extrNative(1055)": 10, "@extrNative(1056)": 10, "@extrNative(1057)": 10, "@extrNative(1058)": 10, "@extrNative(1062)": 1, "@extrUser(addressFromString)": 124, "@extrUser(getBinary)": 14, "@extrUser(getBoolean)": 14, "@extrUser(getInteger)": 14, "@extrUser(getString)": 14, "Address": 1, "Alias": 1, "Asset": 10, "AssetPair": 2, "AttachedPayment": 2, "BalanceDetails": 4, "BinaryEntry": 2, "BlockInfo": 8, "BooleanEntry": 2, "Burn": 2, "BurnTransaction": 10, "Buy": 0, "Ceiling": 0, "CommitToGenerationTransaction": 11, "CreateAliasTransaction": 9, "DataTransaction": 9, "DeleteEntry": 1, "Down": 0, "ExchangeTransaction": 14, "Floor": 0, "GenesisTransaction": 6, "HalfDown": 0, "HalfEven": 0, "HalfUp": 0, "IntegerEntry": 2, "Invocation": 8, "InvokeExpressionTransaction": 10, "InvokeScriptTransaction": 13, "Issue": 7, "IssueTransaction": 14, "Lease": 3, "LeaseCancel": 1, "LeaseCancelTransaction": 9, "LeaseTransaction": 10, "MassTransferTransaction": 13, "Md5": 0, "NoAlg": 0, "Order": 15, "PaymentTransaction": 10, "Reissue": 3, "ReissueTransaction": 11, "ScriptTransfer": 3, "Sell": 0, "SetAssetScriptTransaction": 10, "SetScriptTransaction": 9, "Sha1": 0, "Sha224": 0, "Sha256": 0, "Sha3224": 0, "Sha3256": 0, "Sha3384": 0, "Sha3512": 0, "Sha384": 0, "Sha512": 0, "SponsorFee": 2, "SponsorFeeTransaction": 10, "StringEntry": 2, "Transfer": 2, "TransferTransaction": 13, "Unit": 0, "Up": 0, "UpdateAssetInfoTransaction": 11, "addressFromPublicKey": 63, "contains": 3, "containsElement": 5, "dropRight": 20, "dropRightBytes": 6, "getBinary": 7, "getBoolean": 7, "getInteger": 7, "getString": 7, "isDefined": 1, "parseIntValue": 2, "sqrt": 2, "sqrtBigInt": 5, "takeRight": 20, "takeRightBytes": 6, "throw": 1, "value": 2, "valueOrElse": 2, "valueOrErrorMessage": 2} -var EvaluationCatalogueV9EvaluatorV1 = map[string]int{"!": 1, "!=": 1, "-": 1, "0": 1, "1": 1, "100": 1, "1001": 20, "1004": 15, "1005": 5, "1006": 60, "1007": 10, "1008": 10, "1009": 200, "101": 1, "102": 1, "1020": 75, "1021": 75, "103": 1, "104": 1, "1040": 10, "1041": 10, "1042": 10, "1043": 10, "105": 1, "1050": 10, "1051": 10, "1052": 10, "1053": 10, "1054": 10, "1055": 10, "1056": 10, "1057": 10, "1058": 10, "106": 1, "1060": 5, "1061": 1, "1062": 1, "1063": 1, "107": 1, "1070": 5, "108": 28, "1080": 10, "1081": 1, "109": 100, "1090": 1, "1091": 1, "1092": 1, "1093": 1, "110": 1, "1100": 1, "1101": 1, "1102": 4, "1103": 5, "1104": 5, "1105": 4, "1106": 4, "1107": 2, "118": 270, "119": 200, "1200": 7, "1201": 1, "1202": 1, "1203": 3, "1204": 3, "1205": 1, "1206": 2, "1207": 3, "1208": 3, "1209": 1, "1210": 2, "1211": 11, "1212": 4, "1213": 51, "1214": 2, "1215": 2, "1300": 1, "1301": 1, "1302": 1, "1303": 1, "1304": 1, "1305": 1, "1306": 1, "1307": 1, "1308": 1, "1309": 1, "1310": 1, "1311": 1, "1312": 1, "1313": 1, "1314": 1, "1315": 1, "1316": 1, "1317": 1, "1318": 1, "1319": 1, "1320": 1, "1350": 1, "2": 1, "200": 1, "201": 6, "202": 6, "203": 2, "204": 6, "205": 6, "2400": 1200, "2401": 1300, "2402": 1400, "2403": 1500, "2404": 1600, "2405": 1700, "2406": 1800, "2407": 1900, "2408": 2000, "2409": 2100, "2410": 2200, "2411": 2300, "2412": 2400, "2413": 2500, "2414": 2600, "2450": 800, "2451": 850, "2452": 950, "2453": 1000, "2454": 1050, "2455": 1100, "2456": 1150, "2457": 1200, "2458": 1250, "2459": 1300, "2460": 1350, "2461": 1400, "2462": 1450, "2463": 1550, "2464": 1600, "2500": 43, "2501": 50, "2502": 64, "2503": 93, "2504": 150, "2600": 500, "2601": 550, "2602": 625, "2603": 750, "2700": 20, "2701": 39, "2702": 74, "2703": 147, "2800": 13, "2801": 29, "2802": 58, "2803": 115, "2900": 5, "2901": 9, "2902": 17, "2903": 32, "3": 1, "300": 1, "303": 20, "304": 20, "305": 1, "306": 20, "307": 20, "310": 1, "311": 1, "312": 1, "313": 1, "314": 1, "315": 1, "316": 1, "317": 1, "318": 1, "319": 1, "320": 1, "400": 2, "401": 2, "405": 20, "406": 3, "407": 3, "408": 6, "409": 6, "410": 1, "411": 8, "412": 1, "413": 1, "414": 1, "415": 1, "416": 1, "420": 1, "421": 1, "422": 1, "423": 1, "424": 1, "425": 35, "500": 180, "501": 195, "502": 136, "503": 36, "504": 1000, "600": 3, "601": 1, "602": 35, "603": 40, "604": 10, "605": 10, "606": 1, "607": 1, "608": 1, "609": 1, "701": 30, "800": 2700, "801": 1650, "900": 70, "901": 1, "902": 43, "903": 43, "@extrNative(1040)": 10, "@extrNative(1041)": 10, "@extrNative(1042)": 10, "@extrNative(1043)": 10, "@extrNative(1050)": 10, "@extrNative(1051)": 10, "@extrNative(1052)": 10, "@extrNative(1053)": 10, "@extrNative(1055)": 10, "@extrNative(1056)": 10, "@extrNative(1057)": 10, "@extrNative(1058)": 10, "@extrNative(1062)": 1, "@extrUser(addressFromString)": 124, "@extrUser(getBinary)": 10, "@extrUser(getBoolean)": 10, "@extrUser(getInteger)": 10, "@extrUser(getString)": 10, "Address": 0, "Alias": 0, "Asset": 0, "AssetPair": 0, "AttachedPayment": 0, "BalanceDetails": 0, "BinaryEntry": 0, "BlockInfo": 0, "BooleanEntry": 0, "Burn": 0, "BurnTransaction": 0, "Buy": 0, "Ceiling": 0, "CommitToGenerationTransaction": 0, "CreateAliasTransaction": 0, "DataTransaction": 0, "DeleteEntry": 0, "Down": 0, "ExchangeTransaction": 0, "Floor": 0, "GenesisTransaction": 0, "HalfDown": 0, "HalfEven": 0, "HalfUp": 0, "IntegerEntry": 0, "Invocation": 0, "InvokeExpressionTransaction": 0, "InvokeScriptTransaction": 0, "Issue": 0, "IssueTransaction": 0, "Lease": 0, "LeaseCancel": 0, "LeaseCancelTransaction": 0, "LeaseTransaction": 0, "MassTransferTransaction": 0, "Md5": 0, "NoAlg": 0, "Order": 0, "PaymentTransaction": 0, "Reissue": 0, "ReissueTransaction": 0, "ScriptTransfer": 0, "Sell": 0, "SetAssetScriptTransaction": 0, "SetScriptTransaction": 0, "Sha1": 0, "Sha224": 0, "Sha256": 0, "Sha3224": 0, "Sha3256": 0, "Sha3384": 0, "Sha3512": 0, "Sha384": 0, "Sha512": 0, "SponsorFee": 0, "SponsorFeeTransaction": 0, "StringEntry": 0, "Transfer": 0, "TransferTransaction": 0, "Unit": 0, "Up": 0, "UpdateAssetInfoTransaction": 0, "addressFromPublicKey": 63, "contains": 3, "containsElement": 5, "dropRight": 20, "dropRightBytes": 6, "getBinary": 30, "getBoolean": 30, "getInteger": 30, "getString": 30, "isDefined": 1, "parseIntValue": 2, "sqrt": 2, "sqrtBigInt": 5, "takeRight": 20, "takeRightBytes": 6, "throw": 1, "value": 2, "valueOrElse": 2, "valueOrErrorMessage": 2} -var EvaluationCatalogueV9EvaluatorV2 = map[string]int{"!": 1, "!=": 1, "-": 1, "0": 1, "1": 1, "100": 1, "1001": 20, "1004": 15, "1005": 5, "1006": 60, "1007": 10, "1008": 10, "1009": 200, "101": 1, "102": 1, "1020": 75, "1021": 75, "103": 1, "104": 1, "1040": 10, "1041": 10, "1042": 10, "1043": 10, "105": 1, "1050": 10, "1051": 10, "1052": 10, "1053": 10, "1054": 10, "1055": 10, "1056": 10, "1057": 10, "1058": 10, "106": 1, "1060": 5, "1061": 1, "1062": 1, "1063": 1, "107": 1, "1070": 5, "108": 28, "1080": 10, "1081": 1, "109": 100, "1090": 1, "1091": 1, "1092": 1, "1093": 1, "110": 1, "1100": 1, "1101": 1, "1102": 4, "1103": 5, "1104": 5, "1105": 4, "1106": 4, "1107": 2, "118": 270, "119": 200, "1200": 7, "1201": 1, "1202": 1, "1203": 3, "1204": 3, "1205": 1, "1206": 2, "1207": 3, "1208": 3, "1209": 1, "1210": 2, "1211": 11, "1212": 4, "1213": 51, "1214": 2, "1215": 2, "1300": 1, "1301": 1, "1302": 1, "1303": 1, "1304": 1, "1305": 1, "1306": 1, "1307": 1, "1308": 1, "1309": 1, "1310": 1, "1311": 1, "1312": 1, "1313": 1, "1314": 1, "1315": 1, "1316": 1, "1317": 1, "1318": 1, "1319": 1, "1320": 1, "1350": 1, "2": 1, "200": 1, "201": 6, "202": 6, "203": 2, "204": 6, "205": 6, "2400": 1200, "2401": 1300, "2402": 1400, "2403": 1500, "2404": 1600, "2405": 1700, "2406": 1800, "2407": 1900, "2408": 2000, "2409": 2100, "2410": 2200, "2411": 2300, "2412": 2400, "2413": 2500, "2414": 2600, "2450": 800, "2451": 850, "2452": 950, "2453": 1000, "2454": 1050, "2455": 1100, "2456": 1150, "2457": 1200, "2458": 1250, "2459": 1300, "2460": 1350, "2461": 1400, "2462": 1450, "2463": 1550, "2464": 1600, "2500": 43, "2501": 50, "2502": 64, "2503": 93, "2504": 150, "2600": 500, "2601": 550, "2602": 625, "2603": 750, "2700": 20, "2701": 39, "2702": 74, "2703": 147, "2800": 13, "2801": 29, "2802": 58, "2803": 115, "2900": 5, "2901": 9, "2902": 17, "2903": 32, "3": 1, "300": 1, "303": 20, "304": 20, "305": 1, "306": 20, "307": 20, "310": 1, "311": 1, "312": 1, "313": 1, "314": 1, "315": 1, "316": 1, "317": 1, "318": 1, "319": 1, "320": 1, "400": 2, "401": 2, "405": 20, "406": 3, "407": 3, "408": 6, "409": 6, "410": 1, "411": 8, "412": 1, "413": 1, "414": 1, "415": 1, "416": 1, "420": 1, "421": 1, "422": 1, "423": 1, "424": 1, "425": 35, "500": 180, "501": 195, "502": 136, "503": 36, "504": 1000, "600": 3, "601": 1, "602": 35, "603": 40, "604": 10, "605": 10, "606": 1, "607": 1, "608": 1, "609": 1, "701": 30, "800": 2700, "801": 1650, "900": 70, "901": 1, "902": 43, "903": 43, "@extrNative(1040)": 10, "@extrNative(1041)": 10, "@extrNative(1042)": 10, "@extrNative(1043)": 10, "@extrNative(1050)": 10, "@extrNative(1051)": 10, "@extrNative(1052)": 10, "@extrNative(1053)": 10, "@extrNative(1055)": 10, "@extrNative(1056)": 10, "@extrNative(1057)": 10, "@extrNative(1058)": 10, "@extrNative(1062)": 1, "@extrUser(addressFromString)": 124, "@extrUser(getBinary)": 10, "@extrUser(getBoolean)": 10, "@extrUser(getInteger)": 10, "@extrUser(getString)": 10, "Address": 1, "Alias": 1, "Asset": 1, "AssetPair": 1, "AttachedPayment": 1, "BalanceDetails": 1, "BinaryEntry": 1, "BlockInfo": 1, "BooleanEntry": 1, "Burn": 1, "BurnTransaction": 1, "Buy": 0, "Ceiling": 1, "CommitToGenerationTransaction": 1, "CreateAliasTransaction": 1, "DataTransaction": 1, "DeleteEntry": 1, "Down": 1, "ExchangeTransaction": 1, "Floor": 1, "GenesisTransaction": 1, "HalfDown": 0, "HalfEven": 1, "HalfUp": 1, "IntegerEntry": 1, "Invocation": 1, "InvokeExpressionTransaction": 1, "InvokeScriptTransaction": 1, "Issue": 1, "IssueTransaction": 1, "Lease": 1, "LeaseCancel": 1, "LeaseCancelTransaction": 1, "LeaseTransaction": 1, "MassTransferTransaction": 1, "Md5": 1, "NoAlg": 1, "Order": 1, "PaymentTransaction": 1, "Reissue": 1, "ReissueTransaction": 1, "ScriptTransfer": 1, "Sell": 0, "SetAssetScriptTransaction": 1, "SetScriptTransaction": 1, "Sha1": 1, "Sha224": 1, "Sha256": 1, "Sha3224": 1, "Sha3256": 1, "Sha3384": 1, "Sha3512": 1, "Sha384": 1, "Sha512": 1, "SponsorFee": 1, "SponsorFeeTransaction": 1, "StringEntry": 1, "Transfer": 1, "TransferTransaction": 1, "Unit": 1, "Up": 0, "UpdateAssetInfoTransaction": 1, "addressFromPublicKey": 63, "contains": 3, "containsElement": 5, "dropRight": 20, "dropRightBytes": 6, "getBinary": 30, "getBoolean": 30, "getInteger": 30, "getString": 30, "isDefined": 1, "parseIntValue": 2, "sqrt": 2, "sqrtBigInt": 5, "takeRight": 20, "takeRightBytes": 6, "throw": 2, "value": 2, "valueOrElse": 2, "valueOrErrorMessage": 2} +var EvaluationCatalogueV9EvaluatorV1 = map[string]int{"!": 1, "!=": 1, "-": 1, "0": 1, "1": 1, "100": 1, "1001": 20, "1004": 15, "1005": 5, "1006": 60, "1007": 10, "1008": 10, "1009": 200, "101": 1, "102": 1, "1020": 75, "1021": 75, "103": 1, "104": 1, "1040": 10, "1041": 10, "1042": 10, "1043": 10, "105": 1, "1050": 10, "1051": 10, "1052": 10, "1053": 10, "1054": 10, "1055": 10, "1056": 10, "1057": 10, "1058": 10, "106": 1, "1060": 5, "1061": 1, "1062": 1, "1063": 1, "107": 1, "1070": 5, "108": 28, "1080": 10, "1081": 1, "109": 100, "1090": 1, "1091": 1, "1092": 1, "1093": 1, "110": 1, "1100": 1, "1101": 1, "1102": 4, "1103": 5, "1104": 5, "1105": 1, "1106": 1, "1107": 2, "118": 270, "119": 200, "1200": 7, "1201": 1, "1202": 1, "1203": 3, "1204": 3, "1205": 1, "1206": 2, "1207": 3, "1208": 3, "1209": 1, "1210": 2, "1211": 11, "1212": 4, "1213": 51, "1214": 2, "1215": 2, "1300": 1, "1301": 1, "1302": 1, "1303": 1, "1304": 1, "1305": 1, "1306": 1, "1307": 1, "1308": 1, "1309": 1, "1310": 1, "1311": 1, "1312": 1, "1313": 1, "1314": 1, "1315": 1, "1316": 1, "1317": 1, "1318": 1, "1319": 1, "1320": 1, "1350": 1, "2": 1, "200": 1, "201": 2, "202": 2, "203": 2, "204": 2, "205": 2, "2400": 1200, "2401": 1300, "2402": 1400, "2403": 1500, "2404": 1600, "2405": 1700, "2406": 1800, "2407": 1900, "2408": 2000, "2409": 2100, "2410": 2200, "2411": 2300, "2412": 2400, "2413": 2500, "2414": 2600, "2450": 800, "2451": 850, "2452": 950, "2453": 1000, "2454": 1050, "2455": 1100, "2456": 1150, "2457": 1200, "2458": 1250, "2459": 1300, "2460": 1350, "2461": 1400, "2462": 1450, "2463": 1550, "2464": 1600, "2500": 43, "2501": 50, "2502": 64, "2503": 93, "2504": 150, "2600": 500, "2601": 550, "2602": 625, "2603": 750, "2700": 20, "2701": 39, "2702": 74, "2703": 147, "2800": 13, "2801": 29, "2802": 58, "2803": 115, "2900": 5, "2901": 9, "2902": 17, "2903": 32, "3": 1, "300": 1, "303": 2, "304": 2, "305": 1, "306": 2, "307": 2, "310": 1, "311": 1, "312": 1, "313": 1, "314": 1, "315": 1, "316": 1, "317": 1, "318": 1, "319": 1, "320": 1, "400": 1, "401": 1, "405": 20, "406": 3, "407": 3, "408": 6, "409": 6, "410": 1, "411": 8, "412": 1, "413": 1, "414": 1, "415": 1, "416": 1, "420": 1, "421": 1, "422": 1, "423": 1, "424": 1, "425": 35, "500": 180, "501": 195, "502": 136, "503": 36, "504": 1000, "600": 3, "601": 1, "602": 3, "603": 12, "604": 4, "605": 4, "606": 1, "607": 1, "608": 1, "609": 1, "701": 3, "800": 2700, "801": 1650, "900": 70, "901": 1, "902": 43, "903": 43, "@extrNative(1040)": 10, "@extrNative(1041)": 10, "@extrNative(1042)": 10, "@extrNative(1043)": 10, "@extrNative(1050)": 10, "@extrNative(1051)": 10, "@extrNative(1052)": 10, "@extrNative(1053)": 10, "@extrNative(1055)": 10, "@extrNative(1056)": 10, "@extrNative(1057)": 10, "@extrNative(1058)": 10, "@extrNative(1062)": 1, "@extrUser(addressFromString)": 124, "@extrUser(getBinary)": 14, "@extrUser(getBoolean)": 14, "@extrUser(getInteger)": 14, "@extrUser(getString)": 14, "Address": 0, "Alias": 0, "Asset": 0, "AssetPair": 0, "AttachedPayment": 0, "BalanceDetails": 0, "BinaryEntry": 0, "BlockInfo": 0, "BooleanEntry": 0, "Burn": 0, "BurnTransaction": 0, "Buy": 0, "Ceiling": 0, "CommitToGenerationTransaction": 0, "CreateAliasTransaction": 0, "DataTransaction": 0, "DeleteEntry": 0, "Down": 0, "ExchangeTransaction": 0, "Floor": 0, "GenesisTransaction": 0, "HalfDown": 0, "HalfEven": 0, "HalfUp": 0, "IntegerEntry": 0, "Invocation": 0, "InvokeExpressionTransaction": 0, "InvokeScriptTransaction": 0, "Issue": 0, "IssueTransaction": 0, "Lease": 0, "LeaseCancel": 0, "LeaseCancelTransaction": 0, "LeaseTransaction": 0, "MassTransferTransaction": 0, "Md5": 0, "NoAlg": 0, "Order": 0, "PaymentTransaction": 0, "Reissue": 0, "ReissueTransaction": 0, "ScriptTransfer": 0, "Sell": 0, "SetAssetScriptTransaction": 0, "SetScriptTransaction": 0, "Sha1": 0, "Sha224": 0, "Sha256": 0, "Sha3224": 0, "Sha3256": 0, "Sha3384": 0, "Sha3512": 0, "Sha384": 0, "Sha512": 0, "SponsorFee": 0, "SponsorFeeTransaction": 0, "StringEntry": 0, "Transfer": 0, "TransferTransaction": 0, "Unit": 0, "Up": 0, "UpdateAssetInfoTransaction": 0, "addressFromPublicKey": 63, "contains": 3, "containsElement": 5, "dropRight": 20, "dropRightBytes": 6, "getBinary": 7, "getBoolean": 7, "getInteger": 7, "getString": 7, "isDefined": 1, "parseIntValue": 2, "sqrt": 2, "sqrtBigInt": 5, "takeRight": 20, "takeRightBytes": 6, "throw": 1, "value": 2, "valueOrElse": 2, "valueOrErrorMessage": 2} +var EvaluationCatalogueV9EvaluatorV2 = map[string]int{"!": 1, "!=": 1, "-": 1, "0": 1, "1": 1, "100": 1, "1001": 20, "1004": 15, "1005": 5, "1006": 60, "1007": 10, "1008": 10, "1009": 200, "101": 1, "102": 1, "1020": 75, "1021": 75, "103": 1, "104": 1, "1040": 10, "1041": 10, "1042": 10, "1043": 10, "105": 1, "1050": 10, "1051": 10, "1052": 10, "1053": 10, "1054": 10, "1055": 10, "1056": 10, "1057": 10, "1058": 10, "106": 1, "1060": 5, "1061": 1, "1062": 1, "1063": 1, "107": 1, "1070": 5, "108": 28, "1080": 10, "1081": 1, "109": 100, "1090": 1, "1091": 1, "1092": 1, "1093": 1, "110": 1, "1100": 1, "1101": 1, "1102": 4, "1103": 5, "1104": 5, "1105": 1, "1106": 1, "1107": 2, "118": 270, "119": 200, "1200": 7, "1201": 1, "1202": 1, "1203": 3, "1204": 3, "1205": 1, "1206": 2, "1207": 3, "1208": 3, "1209": 1, "1210": 2, "1211": 11, "1212": 4, "1213": 51, "1214": 2, "1215": 2, "1300": 1, "1301": 1, "1302": 1, "1303": 1, "1304": 1, "1305": 1, "1306": 1, "1307": 1, "1308": 1, "1309": 1, "1310": 1, "1311": 1, "1312": 1, "1313": 1, "1314": 1, "1315": 1, "1316": 1, "1317": 1, "1318": 1, "1319": 1, "1320": 1, "1350": 1, "2": 1, "200": 1, "201": 2, "202": 2, "203": 2, "204": 2, "205": 2, "2400": 1200, "2401": 1300, "2402": 1400, "2403": 1500, "2404": 1600, "2405": 1700, "2406": 1800, "2407": 1900, "2408": 2000, "2409": 2100, "2410": 2200, "2411": 2300, "2412": 2400, "2413": 2500, "2414": 2600, "2450": 800, "2451": 850, "2452": 950, "2453": 1000, "2454": 1050, "2455": 1100, "2456": 1150, "2457": 1200, "2458": 1250, "2459": 1300, "2460": 1350, "2461": 1400, "2462": 1450, "2463": 1550, "2464": 1600, "2500": 43, "2501": 50, "2502": 64, "2503": 93, "2504": 150, "2600": 500, "2601": 550, "2602": 625, "2603": 750, "2700": 20, "2701": 39, "2702": 74, "2703": 147, "2800": 13, "2801": 29, "2802": 58, "2803": 115, "2900": 5, "2901": 9, "2902": 17, "2903": 32, "3": 1, "300": 1, "303": 2, "304": 2, "305": 1, "306": 2, "307": 2, "310": 1, "311": 1, "312": 1, "313": 1, "314": 1, "315": 1, "316": 1, "317": 1, "318": 1, "319": 1, "320": 1, "400": 1, "401": 1, "405": 20, "406": 3, "407": 3, "408": 6, "409": 6, "410": 1, "411": 8, "412": 1, "413": 1, "414": 1, "415": 1, "416": 1, "420": 1, "421": 1, "422": 1, "423": 1, "424": 1, "425": 35, "500": 180, "501": 195, "502": 136, "503": 36, "504": 1000, "600": 3, "601": 1, "602": 3, "603": 12, "604": 4, "605": 4, "606": 1, "607": 1, "608": 1, "609": 1, "701": 3, "800": 2700, "801": 1650, "900": 70, "901": 1, "902": 43, "903": 43, "@extrNative(1040)": 10, "@extrNative(1041)": 10, "@extrNative(1042)": 10, "@extrNative(1043)": 10, "@extrNative(1050)": 10, "@extrNative(1051)": 10, "@extrNative(1052)": 10, "@extrNative(1053)": 10, "@extrNative(1055)": 10, "@extrNative(1056)": 10, "@extrNative(1057)": 10, "@extrNative(1058)": 10, "@extrNative(1062)": 1, "@extrUser(addressFromString)": 124, "@extrUser(getBinary)": 14, "@extrUser(getBoolean)": 14, "@extrUser(getInteger)": 14, "@extrUser(getString)": 14, "Address": 1, "Alias": 1, "Asset": 1, "AssetPair": 1, "AttachedPayment": 1, "BalanceDetails": 1, "BinaryEntry": 1, "BlockInfo": 1, "BooleanEntry": 1, "Burn": 1, "BurnTransaction": 1, "Buy": 0, "Ceiling": 1, "CommitToGenerationTransaction": 1, "CreateAliasTransaction": 1, "DataTransaction": 1, "DeleteEntry": 1, "Down": 1, "ExchangeTransaction": 1, "Floor": 1, "GenesisTransaction": 1, "HalfDown": 0, "HalfEven": 1, "HalfUp": 1, "IntegerEntry": 1, "Invocation": 1, "InvokeExpressionTransaction": 1, "InvokeScriptTransaction": 1, "Issue": 1, "IssueTransaction": 1, "Lease": 1, "LeaseCancel": 1, "LeaseCancelTransaction": 1, "LeaseTransaction": 1, "MassTransferTransaction": 1, "Md5": 1, "NoAlg": 1, "Order": 1, "PaymentTransaction": 1, "Reissue": 1, "ReissueTransaction": 1, "ScriptTransfer": 1, "Sell": 0, "SetAssetScriptTransaction": 1, "SetScriptTransaction": 1, "Sha1": 1, "Sha224": 1, "Sha256": 1, "Sha3224": 1, "Sha3256": 1, "Sha3384": 1, "Sha3512": 1, "Sha384": 1, "Sha512": 1, "SponsorFee": 1, "SponsorFeeTransaction": 1, "StringEntry": 1, "Transfer": 1, "TransferTransaction": 1, "Unit": 1, "Up": 0, "UpdateAssetInfoTransaction": 1, "addressFromPublicKey": 63, "contains": 3, "containsElement": 5, "dropRight": 20, "dropRightBytes": 6, "getBinary": 7, "getBoolean": 7, "getInteger": 7, "getString": 7, "isDefined": 1, "parseIntValue": 2, "sqrt": 2, "sqrtBigInt": 5, "takeRight": 20, "takeRightBytes": 6, "throw": 2, "value": 2, "valueOrElse": 2, "valueOrErrorMessage": 2} const _names_V9 = "!!=-01100100110041005100610071008100910110210201021103104104010411042104310510501051105210531054105510561057105810610601061106210631071070108108010811091090109110921093110110011011102110311041105110611071181191200120112021203120412051206120712081209121012111212121312141215130013011302130313041305130613071308130913101311131213131314131513161317131813191320135022002012022032042052400240124022403240424052406240724082409241024112412241324142450245124522453245424552456245724582459246024612462246324642500250125022503250426002601260226032700270127022703280028012802280329002901290229033300303304305306307310311312313314315316317318319320400401405406407408409410411412413414415416420421422423424425500501502503504600601602603604605606607608609701800801900901902903@extrNative(1040)@extrNative(1041)@extrNative(1042)@extrNative(1043)@extrNative(1050)@extrNative(1051)@extrNative(1052)@extrNative(1053)@extrNative(1055)@extrNative(1056)@extrNative(1057)@extrNative(1058)@extrNative(1062)@extrUser(addressFromString)@extrUser(getBinary)@extrUser(getBoolean)@extrUser(getInteger)@extrUser(getString)AddressAliasAssetAssetPairAttachedPaymentBalanceDetailsBinaryEntryBlockInfoBooleanEntryBurnBurnTransactionBuyCeilingCommitToGenerationTransactionCreateAliasTransactionDataTransactionDeleteEntryDownExchangeTransactionFloorGenesisTransactionHalfDownHalfEvenHalfUpIntegerEntryInvocationInvokeExpressionTransactionInvokeScriptTransactionIssueIssueTransactionLeaseLeaseCancelLeaseCancelTransactionLeaseTransactionMassTransferTransactionMd5NoAlgOrderPaymentTransactionReissueReissueTransactionScriptTransferSellSetAssetScriptTransactionSetScriptTransactionSha1Sha224Sha256Sha3224Sha3256Sha3384Sha3512Sha384Sha512SponsorFeeSponsorFeeTransactionStringEntryTransferTransferTransactionUnitUpUpdateAssetInfoTransactionaddressFromPublicKeycontainscontainsElementdropRightdropRightBytesgetBinarygetBooleangetIntegergetStringisDefinedparseIntValuesqrtsqrtBigInttakeRighttakeRightBytesthrowvaluevalueOrElsevalueOrErrorMessage" diff --git a/pkg/ride/generate/internal/functions_generation.go b/pkg/ride/generate/internal/functions_generation.go index dbef8b743b..29fbb97f2a 100644 --- a/pkg/ride/generate/internal/functions_generation.go +++ b/pkg/ride/generate/internal/functions_generation.go @@ -926,19 +926,46 @@ func functionsV9() map[string]string { func catalogueV9() map[string]int { m := catalogueV8() + m["201"] = 2 + m["202"] = 2 + m["204"] = 2 + m["205"] = 2 + m["303"] = 2 + m["304"] = 2 + m["306"] = 2 + m["307"] = 2 + m["400"] = 1 + m["401"] = 1 m["503"] = 36 for i, c := range []int{5, 9, 17, 32} { m[strconv.Itoa(sha256BaseID+i)] = c } - m["606"] = 1 - m["607"] = 1 - m["608"] = 1 - m["609"] = 1 + m["602"] = 3 + m["603"] = 12 + m["604"] = 4 + m["605"] = 4 + m["606"] = 1 // New function ToBase64_1C + m["607"] = 1 // New function FromBase64_1C + m["608"] = 1 // New function ToBase16_1C + m["609"] = 1 // New function FromBase16_1C + m["701"] = 3 m["902"] = 43 m["903"] = 43 + m["1105"] = 1 + m["1106"] = 1 m["1107"] = 2 - m["1214"] = 2 - m["1215"] = 2 + m["1214"] = 2 // New function ReplaceFirst + m["1215"] = 2 // New function ReplaceAll + + m["getInteger"] = 7 + m["getBoolean"] = 7 + m["getBinary"] = 7 + m["getString"] = 7 + m["@extrUser(getInteger)"] = 14 + m["@extrUser(getBoolean)"] = 14 + m["@extrUser(getBinary)"] = 14 + m["@extrUser(getString)"] = 14 + constructorsCatalogue(ast.LibV9, m) return m } diff --git a/pkg/ride/tree_estimation_test.go b/pkg/ride/tree_estimation_test.go index 912e7966ef..c89456bb68 100644 --- a/pkg/ride/tree_estimation_test.go +++ b/pkg/ride/tree_estimation_test.go @@ -661,3 +661,20 @@ func TestValidateTDXCertificatesChainEstimationDApp(t *testing.T) { }) } } + +func TestRideV9ScalaCompatibility(t *testing.T) { + code := "CQItCAISAwoBCBIHCgUBAQERGBIHCgUBCAEIARIHCgUBEhERCBIDCgEBEgASABIAJQAHV2F2ZXNJZAIFV0FWRVMAEFB1enpsZUJ1eWJhY2tGZWUAGQARUGF5b3V0RW1wdHlTdGF0dXMAAAAVUGF5b3V0SW5pdGlhdGVkU3RhdHVzAAEAFVBheW91dENvbXBsZXRlZFN0YXR1cwACABJCbG9ja3NCYXRjaE1heFNpemUACgAWUmVjaXBpZW50c0JhdGNoTWF4U2l6ZQBkAAZIZWlnaHQFBmhlaWdodAAFQXJyMTAJAMwIAgAACQDMCAIAAQkAzAgCAAIJAMwIAgADCQDMCAIABAkAzAgCAAUJAMwIAgAGCQDMCAIABwkAzAgCAAgJAMwIAgAJBQNuaWwAEENvbmZpZ0FkZHJlc3NLZXkCDWNvbmZpZ0FkZHJlc3MADUxhc3RQYXlvdXRLZXkCCmxhc3RQYXlvdXQAE01hc3NUcmFuc2ZlckZlZXNLZXkCEG1hc3NUcmFuc2ZlckZlZXMAD1dhdmVzQ2hhaW5JZEtleQkArAICAghjaGFpbklkXwUHV2F2ZXNJZAAQRGFlbW9uQWRkcmVzc0tleQINZGFlbW9uQWRkcmVzcwASRGFlbW9uUHVibGljS2V5S2V5Ag9kYWVtb25QdWJsaWNLZXkAFE1haW50YWluZXJBZGRyZXNzS2V5AhFtYWludGFpbmVyQWRkcmVzcwAaUHV6emxlQWdncmVnYXRvckFkZHJlc3NLZXkCF3B1enpsZUFnZ3JlZ2F0b3JBZGRyZXNzABdQdXp6bGVCdXliYWNrQWRkcmVzc0tleQIUcHV6emxlQnV5YmFja0FkZHJlc3MAElNldHRpbmdzQWRkcmVzc0tleQIPc2V0dGluZ3NBZGRyZXNzABJTaWduZXJQdWJsaWNLZXlLZXkCD3NpZ25lclB1YmxpY0tleQAZV2F2ZXNEYW9GYWN0b3J5QWRkcmVzc0tleQIWd2F2ZXNEYW9GYWN0b3J5QWRkcmVzcwANQ29uZmlnQWRkcmVzcwkBEUBleHRyTmF0aXZlKDEwNjIpAQkBEUBleHRyTmF0aXZlKDEwNTMpAgUEdGhpcwUQQ29uZmlnQWRkcmVzc0tleQAMV2F2ZXNDaGFpbklkCQERQGV4dHJOYXRpdmUoMTA1MCkCBQ1Db25maWdBZGRyZXNzBQ9XYXZlc0NoYWluSWRLZXkADURhZW1vbkFkZHJlc3MJARFAZXh0ck5hdGl2ZSgxMDYyKQEJARFAZXh0ck5hdGl2ZSgxMDUzKQIFDUNvbmZpZ0FkZHJlc3MFEERhZW1vbkFkZHJlc3NLZXkAD0RhZW1vblB1YmxpY0tleQkBEUBleHRyTmF0aXZlKDEwNTIpAgUNQ29uZmlnQWRkcmVzcwUSRGFlbW9uUHVibGljS2V5S2V5ABFNYWludGFpbmVyQWRkcmVzcwkBEUBleHRyTmF0aXZlKDEwNjIpAQkBEUBleHRyTmF0aXZlKDEwNTMpAgUNQ29uZmlnQWRkcmVzcwUUTWFpbnRhaW5lckFkZHJlc3NLZXkAF1B1enpsZUFnZ3JlZ2F0b3JBZGRyZXNzCQERQGV4dHJOYXRpdmUoMTA2MikBCQERQGV4dHJOYXRpdmUoMTA1MykCBQ1Db25maWdBZGRyZXNzBRpQdXp6bGVBZ2dyZWdhdG9yQWRkcmVzc0tleQAUUHV6emxlQnV5YmFja0FkZHJlc3MJARFAZXh0ck5hdGl2ZSgxMDYyKQEJARFAZXh0ck5hdGl2ZSgxMDUzKQIFDUNvbmZpZ0FkZHJlc3MFF1B1enpsZUJ1eWJhY2tBZGRyZXNzS2V5AA9TaWduZXJQdWJsaWNLZXkJAJwIAgUNQ29uZmlnQWRkcmVzcwUSU2lnbmVyUHVibGljS2V5S2V5ABZXYXZlc0Rhb0ZhY3RvcnlBZGRyZXNzCQERQGV4dHJOYXRpdmUoMTA2MikBCQERQGV4dHJOYXRpdmUoMTA1MykCBQ1Db25maWdBZGRyZXNzBRlXYXZlc0Rhb0ZhY3RvcnlBZGRyZXNzS2V5ARFtYWtlUGF5b3V0RGF0YUtleQEIcGF5b3V0SWQJAKwCAgILcGF5b3V0RGF0YV8JAKQDAQUIcGF5b3V0SWQBE21ha2VQYXlvdXRTdGF0dXNLZXkBCHBheW91dElkCQCsAgICDXBheW91dFN0YXR1c18JAKQDAQUIcGF5b3V0SWQBHm1ha2VQYXlvdXRBZGRpdGlvbmFsUmV3YXJkc0tleQEIcGF5b3V0SWQJAKwCAgIYcGF5b3V0QWRkaXRpb25hbFJld2FyZHNfCQCkAwEFCHBheW91dElkAQ1nZXRMYXN0UGF5b3V0AAkBEUBleHRyTmF0aXZlKDEwNTApAgUEdGhpcwUNTGFzdFBheW91dEtleQENZ2V0UGF5b3V0RGF0YQEIcGF5b3V0SWQEDHBheW91dFN0cmluZwkBEUBleHRyTmF0aXZlKDEwNTgpAQkBEW1ha2VQYXlvdXREYXRhS2V5AQUIcGF5b3V0SWQEDnBheW91dERhdGFMaXN0CQC1CQIFDHBheW91dFN0cmluZwIBXwQLc3RhcnRIZWlnaHQJAQ1wYXJzZUludFZhbHVlAQkAkQMCBQ5wYXlvdXREYXRhTGlzdAAABAllbmRIZWlnaHQJAQ1wYXJzZUludFZhbHVlAQkAkQMCBQ5wYXlvdXREYXRhTGlzdAABCQCUCgIFC3N0YXJ0SGVpZ2h0BQllbmRIZWlnaHQBD2dldFBheW91dFN0YXR1cwEIcGF5b3V0SWQJAQt2YWx1ZU9yRWxzZQIJAJ8IAQkBE21ha2VQYXlvdXRTdGF0dXNLZXkBBQhwYXlvdXRJZAURUGF5b3V0RW1wdHlTdGF0dXMBGmdldFBheW91dEFkZGl0aW9uYWxSZXdhcmRzAQhwYXlvdXRJZAkBC3ZhbHVlT3JFbHNlAgkAnwgBCQEebWFrZVBheW91dEFkZGl0aW9uYWxSZXdhcmRzS2V5AQUIcGF5b3V0SWQAAAgBaQEFc2V0dXABDWNvbmZpZ0FkZHJlc3MDCQECIT0CCAUBaQZjYWxsZXIFBHRoaXMJAAIBAg1BY2Nlc3MgZGVuaWVkAwkBAiE9AgkAkAMBCAUBaQhwYXltZW50cwAACQACAQIXUGF5bWVudHMgYXJlIHByb2hpYml0ZWQDCQAAAgkApggBBQ1jb25maWdBZGRyZXNzBQR1bml0CQACAQkArAICAhdJbnZhbGlkIGNvbmZpZ0FkZHJlc3M6IAUNY29uZmlnQWRkcmVzcwkAzAgCCQELU3RyaW5nRW50cnkCBRBDb25maWdBZGRyZXNzS2V5BQ1jb25maWdBZGRyZXNzBQNuaWwBaQEOaW5pdGlhdGVQYXlvdXQFCHBheW91dElkC3N0YXJ0SGVpZ2h0CWVuZEhlaWdodAdhbW91bnRzBmFzc2V0cwMJAQIhPQIIBQFpBmNhbGxlcgUNRGFlbW9uQWRkcmVzcwkAAgECDUFjY2VzcyBkZW5pZWQDCQECIT0CCQCQAwEIBQFpCHBheW1lbnRzAAAJAAIBAhdQYXltZW50cyBhcmUgcHJvaGliaXRlZAQKbGFzdFBheW91dAkBDWdldExhc3RQYXlvdXQAAwkBAiE9AgUIcGF5b3V0SWQJAGQCBQpsYXN0UGF5b3V0AAEJAAIBCQCsAgIJAKwCAgkArAICAhBXcm9uZyBwYXlvdXRJZDogCQCkAwEFCHBheW91dElkAg0sIHNob3VsZCBiZTogCQCkAwEJAGQCBQpsYXN0UGF5b3V0AAEEEGxhc3RQYXlvdXRTdGF0dXMJAQ9nZXRQYXlvdXRTdGF0dXMBBQpsYXN0UGF5b3V0AwkBAiE9AgUQbGFzdFBheW91dFN0YXR1cwUVUGF5b3V0Q29tcGxldGVkU3RhdHVzCQACAQkArAICCQCsAgICDUxhc3QgcGF5b3V0OiAJAKQDAQUKbGFzdFBheW91dAISIHdhcyBub3QgY29tcGxldGVkBAskdDAzNjA1MzY2OQkBDWdldFBheW91dERhdGEBBQpsYXN0UGF5b3V0BA9sYXN0U3RhcnRIZWlnaHQIBQskdDAzNjA1MzY2OQJfMQQNbGFzdEVuZEhlaWdodAgFCyR0MDM2MDUzNjY5Al8yAwkBAiE9AgULc3RhcnRIZWlnaHQJAGQCBQ1sYXN0RW5kSGVpZ2h0AAEJAAIBCQCsAgIJAKwCAgkArAICAhNXcm9uZyBzdGFydEhlaWdodDogCQCkAwEFC3N0YXJ0SGVpZ2h0Ag0sIHNob3VsZCBiZTogCQCkAwEJAGQCBQ1sYXN0RW5kSGVpZ2h0AAEDCQBmAgULc3RhcnRIZWlnaHQFCWVuZEhlaWdodAkAAgEJAKwCAgkArAICCQCsAgICC2VuZEhlaWdodDogCQCkAwEFCWVuZEhlaWdodAIYIGxlc3MgdGhhbiBzdGFydEhlaWdodDogCQCkAwEFC3N0YXJ0SGVpZ2h0BAthbW91bnRzU2l6ZQkAkAMBBQdhbW91bnRzBAphc3NldHNTaXplCQCQAwEFBmFzc2V0cwMJAQIhPQIFC2Ftb3VudHNTaXplBQphc3NldHNTaXplCQACAQIfV3Jvbmcgc2l6ZSBvZiBhbW91bnRzIG9yIGFzc2V0cwQaYXZhaWxhYmxlV2F2ZXNEYW9McFRvQ2xhaW0JAQt2YWx1ZU9yRWxzZQIJAJoIAgUWV2F2ZXNEYW9GYWN0b3J5QWRkcmVzcwkArAICAhElcyVzX19hdmFpbGFibGVfXwkApQgBBQR0aGlzAAAEGndhdmVzRGFvTmV4dEJsb2NrVG9Qcm9jZXNzCQERQGV4dHJOYXRpdmUoMTA1MCkCBRZXYXZlc0Rhb0ZhY3RvcnlBZGRyZXNzAhYlc19fbmV4dEJsb2NrVG9Qcm9jZXNzBA13YXZlc0RscENsYWltAwMJAGYCBRphdmFpbGFibGVXYXZlc0Rhb0xwVG9DbGFpbQAACQBnAgkAZAIFGndhdmVzRGFvTmV4dEJsb2NrVG9Qcm9jZXNzBRJCbG9ja3NCYXRjaE1heFNpemUFBkhlaWdodAcJAPwHBAUWV2F2ZXNEYW9GYWN0b3J5QWRkcmVzcwIHY2xhaW1MUAUDbmlsBQNuaWwFBHVuaXQDCQAAAgUNd2F2ZXNEbHBDbGFpbQUNd2F2ZXNEbHBDbGFpbQQYY3VycmVudEFkZGl0aW9uYWxSZXdhcmRzCQEaZ2V0UGF5b3V0QWRkaXRpb25hbFJld2FyZHMBBQhwYXlvdXRJZAoBCmFzc2V0c0ZvbGQCBWFjY3VtBWluZGV4AwkAZwIFBWluZGV4BQphc3NldHNTaXplBQVhY2N1bQQIYXNzZXRTdHIJAJEDAgUGYXNzZXRzBQVpbmRleAMJAAACCQCaCAIFDUNvbmZpZ0FkZHJlc3MJAKwCAgILYXNzZXRJbmRleF8FCGFzc2V0U3RyBQR1bml0CQACAQkArAICAg9Vbmtub3duIGFzc2V0OiAFCGFzc2V0U3RyBAskdDA0OTU1NTEzNAMJAAACBQhhc3NldFN0cgUHV2F2ZXNJZAkAlAoCBQR1bml0CQBkAgkAkQMCBQdhbW91bnRzBQVpbmRleAUYY3VycmVudEFkZGl0aW9uYWxSZXdhcmRzCQCUCgIJANkEAQUIYXNzZXRTdHIJAJEDAgUHYW1vdW50cwUFaW5kZXgEBWFzc2V0CAULJHQwNDk1NTUxMzQCXzEEBmFtb3VudAgFCyR0MDQ5NTU1MTM0Al8yAwkAZgIAAAUGYW1vdW50CQACAQkArAICCQCsAgICFFdyb25nIGFzc2V0IGFtb3VudDogCQCkAwEFBmFtb3VudAIcLCBzaG91bGQgYmUgcG9zaXRpdmUgb3IgemVybwQUbWFpbnRhaW5lckZlZVBlcmNlbnQJARFAZXh0ck5hdGl2ZSgxMDUwKQIFDUNvbmZpZ0FkZHJlc3MJAKwCAgIJYXNzZXRGZWVfBQhhc3NldFN0cgQNbWFpbnRhaW5lckZlZQkAawMFBmFtb3VudAUUbWFpbnRhaW5lckZlZVBlcmNlbnQA6AcEEHB1enpsZUJ1eWJhY2tGZWUJAGsDBQZhbW91bnQFEFB1enpsZUJ1eWJhY2tGZWUA6AcEDWxlc3NvcnNBbW91bnQJAGUCCQBlAgUGYW1vdW50BQ1tYWludGFpbmVyRmVlBRBwdXp6bGVCdXliYWNrRmVlCQCUCgIJAM0IAgkAzQgCCAUFYWNjdW0CXzEJAQ5TY3JpcHRUcmFuc2ZlcgMFEU1haW50YWluZXJBZGRyZXNzBQ1tYWludGFpbmVyRmVlBQVhc3NldAkBDlNjcmlwdFRyYW5zZmVyAwUUUHV6emxlQnV5YmFja0FkZHJlc3MFEHB1enpsZUJ1eWJhY2tGZWUFBWFzc2V0CQDNCAIIBQVhY2N1bQJfMgkApAMBBQ1sZXNzb3JzQW1vdW50BAskdDA1ODAwNTg2NwoAAiRsBQVBcnIxMAoAAiRzCQCQAwEFAiRsCgAFJGFjYzAJAJQKAgUDbmlsBQNuaWwKAQUkZjBfMQICJGECJGkDCQBnAgUCJGkFAiRzBQIkYQkBCmFzc2V0c0ZvbGQCBQIkYQkAkQMCBQIkbAUCJGkKAQUkZjBfMgICJGECJGkDCQBnAgUCJGkFAiRzBQIkYQkAAgECFExpc3Qgc2l6ZSBleGNlZWRzIDEwCQEFJGYwXzICCQEFJGYwXzECCQEFJGYwXzECCQEFJGYwXzECCQEFJGYwXzECCQEFJGYwXzECCQEFJGYwXzECCQEFJGYwXzECCQEFJGYwXzECCQEFJGYwXzECCQEFJGYwXzECBQUkYWNjMAAAAAEAAgADAAQABQAGAAcACAAJAAoECXRyYW5zZmVycwgFCyR0MDU4MDA1ODY3Al8xBAphbW91bnRLZXlzCAULJHQwNTgwMDU4NjcCXzIEDW5ld1BheW91dERhdGEJALkJAgkAzggCCQDMCAIJAKQDAQULc3RhcnRIZWlnaHQJAMwIAgkApAMBBQllbmRIZWlnaHQFA25pbAUKYW1vdW50S2V5cwIBXwkAzggCBQl0cmFuc2ZlcnMJAMwIAgkBDEludGVnZXJFbnRyeQIFDUxhc3RQYXlvdXRLZXkFCHBheW91dElkCQDMCAIJAQtTdHJpbmdFbnRyeQIJARFtYWtlUGF5b3V0RGF0YUtleQEFCHBheW91dElkBQ1uZXdQYXlvdXREYXRhCQDMCAIJAQxJbnRlZ2VyRW50cnkCCQETbWFrZVBheW91dFN0YXR1c0tleQEFCHBheW91dElkBRVQYXlvdXRJbml0aWF0ZWRTdGF0dXMFA25pbAkAAgECJFN0cmljdCB2YWx1ZSBpcyBub3QgZXF1YWwgdG8gaXRzZWxmLgFpAQRzd2FwBQhwYXlvdXRJZAhzd2FwRnJvbQhhbW91bnRJbgVyb3V0ZQxtaW5Ub1JlY2VpdmUDCQECIT0CCAUBaQZjYWxsZXIFDURhZW1vbkFkZHJlc3MJAAIBAg1BY2Nlc3MgZGVuaWVkAwkBAiE9AgkAkAMBCAUBaQhwYXltZW50cwAACQACAQIXUGF5bWVudHMgYXJlIHByb2hpYml0ZWQECmxhc3RQYXlvdXQJAQ1nZXRMYXN0UGF5b3V0AAMJAQIhPQIFCmxhc3RQYXlvdXQFCHBheW91dElkCQACAQkArAICCQCsAgIJAKwCAgIQV3JvbmcgcGF5b3V0SWQ6IAkApAMBBQhwYXlvdXRJZAINLCBzaG91bGQgYmU6IAkApAMBBQpsYXN0UGF5b3V0BAxwYXlvdXRTdGF0dXMJAQ9nZXRQYXlvdXRTdGF0dXMBBQhwYXlvdXRJZAMJAQIhPQIFDHBheW91dFN0YXR1cwUVUGF5b3V0SW5pdGlhdGVkU3RhdHVzCQACAQkArAICCQCsAgICFVdyb25nIHBheW91dCBzdGF0dXM6IAkApAMBBQxwYXlvdXRTdGF0dXMCDSwgc2hvdWxkIGJlIDEECyR0MDY4MTg3MDY3AwkAAAIFCHN3YXBGcm9tBQdXYXZlc0lkCQCUCgIICQDvBwEFBHRoaXMJYXZhaWxhYmxlBQR1bml0BAdhc3NldElkCQDZBAEFCHN3YXBGcm9tCQCUCgIJAPAHAgUEdGhpcwUHYXNzZXRJZAUHYXNzZXRJZAQTYmFsYW5jZUluQmVmb3JlU3dhcAgFCyR0MDY4MTg3MDY3Al8xBApzd2FwRnJvbUlkCAULJHQwNjgxODcwNjcCXzIECnN3YXBJbnZva2UJAPwHBAUXUHV6emxlQWdncmVnYXRvckFkZHJlc3MCEHN3YXBXaXRoUmVmZXJyYWwJAMwIAgUFcm91dGUJAMwIAgUMbWluVG9SZWNlaXZlCQDMCAICCnB1enpsZW5vZGUFA25pbAkAzAgCCQEPQXR0YWNoZWRQYXltZW50AgUKc3dhcEZyb21JZAUIYW1vdW50SW4FA25pbAMJAAACBQpzd2FwSW52b2tlBQpzd2FwSW52b2tlBBJiYWxhbmNlSW5BZnRlclN3YXADCQAAAgUIc3dhcEZyb20FB1dhdmVzSWQICQDvBwEFBHRoaXMJYXZhaWxhYmxlCQDwBwIFBHRoaXMJAQV2YWx1ZQEFCnN3YXBGcm9tSWQEBnJlZnVuZAkAZQIFEmJhbGFuY2VJbkFmdGVyU3dhcAkAZQIFE2JhbGFuY2VJbkJlZm9yZVN3YXAFCGFtb3VudEluAwkAZgIFBnJlZnVuZAAKCQACAQkArAICAhxSb2xsYmFjayBhZ2dyZWdhdG9yIHJlZnVuZDogCQCkAwEFBnJlZnVuZAUDbmlsCQACAQIkU3RyaWN0IHZhbHVlIGlzIG5vdCBlcXVhbCB0byBpdHNlbGYuAWkBEGRpc3RyaWJ1dGVUb2tlbnMFCHBheW91dElkCnJlY2lwaWVudHMHYW1vdW50cwZhc3NldHMKYXR0YWNobWVudAMJAQIhPQIIBQFpBmNhbGxlcgUNRGFlbW9uQWRkcmVzcwkAAgECDUFjY2VzcyBkZW5pZWQDCQECIT0CCQCQAwEIBQFpCHBheW1lbnRzAAAJAAIBAhdQYXltZW50cyBhcmUgcHJvaGliaXRlZAQKbGFzdFBheW91dAkBDWdldExhc3RQYXlvdXQAAwkBAiE9AgUKbGFzdFBheW91dAUIcGF5b3V0SWQJAAIBCQCsAgIJAKwCAgkArAICAhBXcm9uZyBwYXlvdXRJZDogCQCkAwEFCHBheW91dElkAg0sIHNob3VsZCBiZTogCQCkAwEFCmxhc3RQYXlvdXQEDHBheW91dFN0YXR1cwkBD2dldFBheW91dFN0YXR1cwEFCHBheW91dElkAwkBAiE9AgUMcGF5b3V0U3RhdHVzBRVQYXlvdXRJbml0aWF0ZWRTdGF0dXMJAAIBCQCsAgIJAKwCAgIVV3JvbmcgcGF5b3V0IHN0YXR1czogCQCkAwEFDHBheW91dFN0YXR1cwINLCBzaG91bGQgYmUgMQQOcmVjaXBpZW50c1NpemUJAJADAQUKcmVjaXBpZW50cwQLYW1vdW50c1NpemUJAJADAQUHYW1vdW50cwQKYXNzZXRzU2l6ZQkAkAMBBQZhc3NldHMDAwkBAiE9AgUOcmVjaXBpZW50c1NpemUFC2Ftb3VudHNTaXplBgkBAiE9AgUOcmVjaXBpZW50c1NpemUFCmFzc2V0c1NpemUJAAIBCQCsAgIJAKwCAgkArAICCQCsAgIJAKwCAgIXV3JvbmcgcmVjaXBpZW50cyBzaXplOiAJAKQDAQUOcmVjaXBpZW50c1NpemUCECwgYW1vdW50cyBzaXplOiAJAKQDAQULYW1vdW50c1NpemUCDywgYXNzZXRzIHNpemU6IAkApAMBBQphc3NldHNTaXplAwkAAAIFDnJlY2lwaWVudHNTaXplAAAJAAIBAiNSZWNpcGllbnRzIGxpc3Qgc2hvdWxkIG5vdCBiZSBlbXB0eQMJAGYCBQ5yZWNpcGllbnRzU2l6ZQUWUmVjaXBpZW50c0JhdGNoTWF4U2l6ZQkAAgEJAKwCAgkArAICCQCsAgICG1Nob3VsZCBiZSBub3QgZ3JlYXRlciB0aGFuIAkApAMBBRZSZWNpcGllbnRzQmF0Y2hNYXhTaXplAhYgcmVjaXBpZW50cywgY3VycmVudDogCQCkAwEFDnJlY2lwaWVudHNTaXplBAltYWdpY0J5dGUJAMoBAgkAmgMBAAEABwQLY2hhaW5JZEJ5dGUJAMoBAgkAmgMBBQxXYXZlc0NoYWluSWQABwQNYWRkcmVzc1ByZWZpeAkAywECBQltYWdpY0J5dGUFC2NoYWluSWRCeXRlCgETc2NyaXB0VHJhbnNmZXJzRm9sZAIFYWNjdW0FaW5kZXgDCQBnAgUFaW5kZXgFDnJlY2lwaWVudHNTaXplBQVhY2N1bQQNcHVibGljS2V5SGFzaAkAkQMCBQpyZWNpcGllbnRzBQVpbmRleAQLYWRkcmVzc0RhdGEJAMsBAgUNYWRkcmVzc1ByZWZpeAUNcHVibGljS2V5SGFzaAQPYWRkcmVzc0NoZWNrc3VtCQDJAQIJAPUDAQkA9gMBBQthZGRyZXNzRGF0YQAEBAlyZWNpcGllbnQJAQdBZGRyZXNzAQkAywECBQthZGRyZXNzRGF0YQUPYWRkcmVzc0NoZWNrc3VtBAZhbW91bnQJAJEDAgUHYW1vdW50cwUFaW5kZXgECmFzc2V0SW5kZXgJAJEDAgUGYXNzZXRzBQVpbmRleAQHYXNzZXRJZAkBEUBleHRyTmF0aXZlKDEwNTIpAgUNQ29uZmlnQWRkcmVzcwkArAICAghhc3NldElkXwkApAMBBQphc3NldEluZGV4BAVhc3NldAMJAAACBQdhc3NldElkAQAFBHVuaXQFB2Fzc2V0SWQJAM0IAgUFYWNjdW0JAQ5TY3JpcHRUcmFuc2ZlcgMFCXJlY2lwaWVudAUGYW1vdW50BQVhc3NldAQHaW5kZXhlcwkAzAgCAAAJAMwIAgABCQDMCAIAAgkAzAgCAAMJAMwIAgAECQDMCAIABQkAzAgCAAYJAMwIAgAHCQDMCAIACAkAzAgCAAkJAMwIAgAKCQDMCAIACwkAzAgCAAwJAMwIAgANCQDMCAIADgkAzAgCAA8JAMwIAgAQCQDMCAIAEQkAzAgCABIJAMwIAgATCQDMCAIAFAkAzAgCABUJAMwIAgAWCQDMCAIAFwkAzAgCABgJAMwIAgAZCQDMCAIAGgkAzAgCABsJAMwIAgAcCQDMCAIAHQkAzAgCAB4JAMwIAgAfCQDMCAIAIAkAzAgCACEJAMwIAgAiCQDMCAIAIwkAzAgCACQJAMwIAgAlCQDMCAIAJgkAzAgCACcJAMwIAgAoCQDMCAIAKQkAzAgCACoJAMwIAgArCQDMCAIALAkAzAgCAC0JAMwIAgAuCQDMCAIALwkAzAgCADAJAMwIAgAxCQDMCAIAMgkAzAgCADMJAMwIAgA0CQDMCAIANQkAzAgCADYJAMwIAgA3CQDMCAIAOAkAzAgCADkJAMwIAgA6CQDMCAIAOwkAzAgCADwJAMwIAgA9CQDMCAIAPgkAzAgCAD8JAMwIAgBACQDMCAIAQQkAzAgCAEIJAMwIAgBDCQDMCAIARAkAzAgCAEUJAMwIAgBGCQDMCAIARwkAzAgCAEgJAMwIAgBJCQDMCAIASgkAzAgCAEsJAMwIAgBMCQDMCAIATQkAzAgCAE4JAMwIAgBPCQDMCAIAUAkAzAgCAFEJAMwIAgBSCQDMCAIAUwkAzAgCAFQJAMwIAgBVCQDMCAIAVgkAzAgCAFcJAMwIAgBYCQDMCAIAWQkAzAgCAFoJAMwIAgBbCQDMCAIAXAkAzAgCAF0JAMwIAgBeCQDMCAIAXwkAzAgCAGAJAMwIAgBhCQDMCAIAYgkAzAgCAGMFA25pbAoAAiRsBQdpbmRleGVzCgACJHMJAJADAQUCJGwKAAUkYWNjMAUDbmlsCgEFJGYwXzECAiRhAiRpAwkAZwIFAiRpBQIkcwUCJGEJARNzY3JpcHRUcmFuc2ZlcnNGb2xkAgUCJGEJAJEDAgUCJGwFAiRpCgEFJGYwXzICAiRhAiRpAwkAZwIFAiRpBQIkcwUCJGEJAAIBAhVMaXN0IHNpemUgZXhjZWVkcyAxMDAJAQUkZjBfMgIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIJAQUkZjBfMQIFBSRhY2MwAAAAAQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4AHwAgACEAIgAjACQAJQAmACcAKAApACoAKwAsAC0ALgAvADAAMQAyADMANAA1ADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwBQAFEAUgBTAFQAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZAFpAQ5maW5hbGl6ZVBheW91dAEIcGF5b3V0SWQDCQECIT0CCAUBaQZjYWxsZXIFDURhZW1vbkFkZHJlc3MJAAIBAg1BY2Nlc3MgZGVuaWVkBApsYXN0UGF5b3V0CQENZ2V0TGFzdFBheW91dAADCQECIT0CBQpsYXN0UGF5b3V0BQhwYXlvdXRJZAkAAgEJAKwCAgkArAICCQCsAgICEFdyb25nIHBheW91dElkOiAJAKQDAQUIcGF5b3V0SWQCDSwgc2hvdWxkIGJlOiAJAKQDAQUKbGFzdFBheW91dAQMcGF5b3V0U3RhdHVzCQEPZ2V0UGF5b3V0U3RhdHVzAQUKbGFzdFBheW91dAMJAQIhPQIFDHBheW91dFN0YXR1cwUVUGF5b3V0SW5pdGlhdGVkU3RhdHVzCQACAQkArAICCQCsAgICFVdyb25nIHBheW91dCBzdGF0dXM6IAkApAMBBQxwYXlvdXRTdGF0dXMCDiwgc2hvdWxkIGJlOiAxCQDMCAIJAQxJbnRlZ2VyRW50cnkCCQETbWFrZVBheW91dFN0YXR1c0tleQEFCmxhc3RQYXlvdXQFFVBheW91dENvbXBsZXRlZFN0YXR1cwUDbmlsAWkBFGFkZEFkZGl0aW9uYWxSZXdhcmRzAAMJAQIhPQIJAJADAQgFAWkIcGF5bWVudHMAAQkAAgECJ0F0IGxlYXN0IG9uZSBwYXltZW50IHNob3VsZCBiZSBhdHRhY2hlZAMJAQIhPQIICQCRAwIIBQFpCHBheW1lbnRzAAAHYXNzZXRJZAUEdW5pdAkAAgECGk9ubHkgd2F2ZXMgY2FuIGJlIGF0dGFjaGVkBApsYXN0UGF5b3V0CQENZ2V0TGFzdFBheW91dAAEEGxhc3RQYXlvdXRTdGF0dXMJAQ9nZXRQYXlvdXRTdGF0dXMBBQpsYXN0UGF5b3V0AwkBAiE9AgUQbGFzdFBheW91dFN0YXR1cwUVUGF5b3V0Q29tcGxldGVkU3RhdHVzCQACAQkArAICCQCsAgICDUxhc3QgcGF5b3V0OiAJAKQDAQUKbGFzdFBheW91dAISIHdhcyBub3QgY29tcGxldGVkBAxuZXh0UGF5b3V0SWQJAGQCBQpsYXN0UGF5b3V0AAEEGGN1cnJlbnRBZGRpdGlvbmFsUmV3YXJkcwkBGmdldFBheW91dEFkZGl0aW9uYWxSZXdhcmRzAQUMbmV4dFBheW91dElkBBRuZXdBZGRpdGlvbmFsUmV3YXJkcwkAZAIFGGN1cnJlbnRBZGRpdGlvbmFsUmV3YXJkcwgJAJEDAggFAWkIcGF5bWVudHMAAAZhbW91bnQJAMwIAgkBDEludGVnZXJFbnRyeQIJAR5tYWtlUGF5b3V0QWRkaXRpb25hbFJld2FyZHNLZXkBBQxuZXh0UGF5b3V0SWQFFG5ld0FkZGl0aW9uYWxSZXdhcmRzBQNuaWwBaQETYWRkTWFzc1RyYW5zZmVyRmVlcwADCQECIT0CCQCQAwEIBQFpCHBheW1lbnRzAAEJAAIBAidBdCBsZWFzdCBvbmUgcGF5bWVudCBzaG91bGQgYmUgYXR0YWNoZWQDCQECIT0CCAkAkQMCCAUBaQhwYXltZW50cwAAB2Fzc2V0SWQFBHVuaXQJAAIBAhpPbmx5IHdhdmVzIGNhbiBiZSBhdHRhY2hlZAQXY3VycmVudE1hc3NUcmFuc2ZlckZlZXMJAQt2YWx1ZU9yRWxzZQIJAJ8IAQUTTWFzc1RyYW5zZmVyRmVlc0tleQAABBNuZXdNYXNzVHJhbnNmZXJGZWVzCQBkAgUXY3VycmVudE1hc3NUcmFuc2ZlckZlZXMICQCRAwIIBQFpCHBheW1lbnRzAAAGYW1vdW50CQDMCAIJAQxJbnRlZ2VyRW50cnkCBRNNYXNzVHJhbnNmZXJGZWVzS2V5BRNuZXdNYXNzVHJhbnNmZXJGZWVzBQNuaWwBaQEYd2l0aGRyYXdNYXNzVHJhbnNmZXJGZWVzAAMJAQIhPQIIBQFpBmNhbGxlcgURTWFpbnRhaW5lckFkZHJlc3MJAAIBAg1BY2Nlc3MgZGVuaWVkAwkBAiE9AgkAkAMBCAUBaQhwYXltZW50cwAACQACAQIXUGF5bWVudHMgYXJlIHByb2hpYml0ZWQEF2N1cnJlbnRNYXNzVHJhbnNmZXJGZWVzCQELdmFsdWVPckVsc2UCCQCfCAEFE01hc3NUcmFuc2ZlckZlZXNLZXkAAAMJAAACBRdjdXJyZW50TWFzc1RyYW5zZmVyRmVlcwAACQACAQITTm90aGluZyB0byB3aXRoZHJhdwkAzAgCCQEMSW50ZWdlckVudHJ5AgUTTWFzc1RyYW5zZmVyRmVlc0tleQAACQDMCAIJAQ5TY3JpcHRUcmFuc2ZlcgMFEU1haW50YWluZXJBZGRyZXNzBRdjdXJyZW50TWFzc1RyYW5zZmVyRmVlcwUEdW5pdAUDbmlsAQJ0eAEGdmVyaWZ5AAQHJG1hdGNoMAUCdHgDCQABAgUHJG1hdGNoMAIXTWFzc1RyYW5zZmVyVHJhbnNhY3Rpb24EBm1hc3NUeAUHJG1hdGNoMAQQY29uZmlnQWRkcmVzc1N0cgkAnQgCBQR0aGlzBRBDb25maWdBZGRyZXNzS2V5BA1jb25maWdBZGRyZXNzCQERQGV4dHJOYXRpdmUoMTA2MikBCQEFdmFsdWUBBRBjb25maWdBZGRyZXNzU3RyBAlwdWJsaWNLZXkDCQEJaXNEZWZpbmVkAQUQY29uZmlnQWRkcmVzc1N0cgkBC3ZhbHVlT3JFbHNlAgkAnAgCBQ1jb25maWdBZGRyZXNzBRJEYWVtb25QdWJsaWNLZXlLZXkIBQJ0eA9zZW5kZXJQdWJsaWNLZXkIBQJ0eA9zZW5kZXJQdWJsaWNLZXkJAMQTAwgFBm1hc3NUeAlib2R5Qnl0ZXMJAJEDAggFBm1hc3NUeAZwcm9vZnMAAAUJcHVibGljS2V5AwkAAQIFByRtYXRjaDACF0ludm9rZVNjcmlwdFRyYW5zYWN0aW9uBAhpbnZva2VUeAUHJG1hdGNoMAQQY29uZmlnQWRkcmVzc1N0cgkAnQgCBQR0aGlzBRBDb25maWdBZGRyZXNzS2V5BA1jb25maWdBZGRyZXNzCQERQGV4dHJOYXRpdmUoMTA2MikBCQEFdmFsdWUBBRBjb25maWdBZGRyZXNzU3RyBAp1bml0c0NoYWluCQCdCAIFDWNvbmZpZ0FkZHJlc3MJAKwCAgIPdW5pdHNDaGFpbk5hbWVfCQClCAEKAAFACAUIaW52b2tlVHgEZEFwcAMJAAECBQFAAgdBZGRyZXNzBQFACQACAQkArAICCQADAQUBQAIcIGNvdWxkbid0IGJlIGNhc3QgdG8gQWRkcmVzcwQPc2lnbmVyUHVibGljS2V5CQELdmFsdWVPckVsc2UCCQCcCAIFDWNvbmZpZ0FkZHJlc3MFElNpZ25lclB1YmxpY0tleUtleQgFAnR4D3NlbmRlclB1YmxpY0tleQQJcHVibGljS2V5AwkBCWlzRGVmaW5lZAEFEGNvbmZpZ0FkZHJlc3NTdHIDCQEJaXNEZWZpbmVkAQUKdW5pdHNDaGFpbggFAnR4D3NlbmRlclB1YmxpY0tleQUPc2lnbmVyUHVibGljS2V5CAUCdHgPc2VuZGVyUHVibGljS2V5CQDEEwMIBQhpbnZva2VUeAlib2R5Qnl0ZXMJAJEDAggFCGludm9rZVR4BnByb29mcwAABQlwdWJsaWNLZXkDCQABAgUHJG1hdGNoMAIdQ29tbWl0VG9HZW5lcmF0aW9uVHJhbnNhY3Rpb24ECGNvbW1pdFR4BQckbWF0Y2gwCQDEEwMIBQJ0eAlib2R5Qnl0ZXMJAJEDAggFAnR4BnByb29mcwAACAUCdHgPc2VuZGVyUHVibGljS2V5BBBjb25maWdBZGRyZXNzU3RyCQCdCAIFBHRoaXMFEENvbmZpZ0FkZHJlc3NLZXkEDWNvbmZpZ0FkZHJlc3MJARFAZXh0ck5hdGl2ZSgxMDYyKQEJAQV2YWx1ZQEFEGNvbmZpZ0FkZHJlc3NTdHIECXB1YmxpY0tleQMJAQlpc0RlZmluZWQBBRBjb25maWdBZGRyZXNzU3RyCQELdmFsdWVPckVsc2UCCQCcCAIFDWNvbmZpZ0FkZHJlc3MFElNpZ25lclB1YmxpY0tleUtleQgFAnR4D3NlbmRlclB1YmxpY0tleQgFAnR4D3NlbmRlclB1YmxpY0tleQkAyBMDCAUCdHgJYm9keUJ5dGVzCQCRAwIIBQJ0eAZwcm9vZnMAAAUJcHVibGljS2V5VcMOoA==" + _, tree := parseBase64Script(t, code) + est, err := EstimateTree(tree, 4) + require.NoError(t, err) + assert.Equal(t, 36280, est.Estimation) + assert.Equal(t, 180, est.Verifier) + assert.Equal(t, 738, est.Functions["initiatePayout"]) + assert.Equal(t, 21, est.Functions["addMassTransferFees"]) + assert.Equal(t, 54, est.Functions["finalizePayout"]) + assert.Equal(t, 8, est.Functions["setup"]) + assert.Equal(t, 45, est.Functions["withdrawMassTransferFees"]) + assert.Equal(t, 176, est.Functions["swap"]) + assert.Equal(t, 51, est.Functions["addAdditionalRewards"]) + assert.Equal(t, 36280, est.Functions["distributeTokens"]) +} diff --git a/pkg/state/api.go b/pkg/state/api.go index 7821fc094f..4a85c26f96 100644 --- a/pkg/state/api.go +++ b/pkg/state/api.go @@ -158,11 +158,12 @@ type StateInfo interface { // FindGenerator returns the first generator for which the lookup function returns true. // The lookup function receives a copy of GeneratorInfo, so it cannot modify the stored data. - FindGenerator(func(GeneratorInfo) bool) (GeneratorInfo, error) - CommittedGenerators(height proto.Height) ([]GeneratorInfo, error) + FindGenerator(proto.Height, func(GeneratorInfo) bool) (GeneratorInfo, error) + CommittedGenerators(proto.Height) ([]GeneratorInfo, error) LastFinalizedHeight() (proto.Height, error) LastFinalizedBlock() (*proto.BlockHeader, error) - CheckRollbackHeightAuto(height proto.Height) error + CheckRollbackHeightAuto(proto.Height) error + BuildLocalEndorsementMessage(proto.Height, proto.BlockID) (proto.EndorsementCryptoMessage, error) } // StateModifier contains all the methods needed to modify node's state. diff --git a/pkg/state/appender.go b/pkg/state/appender.go index e5118a58c7..ee0caae1e6 100644 --- a/pkg/state/appender.go +++ b/pkg/state/appender.go @@ -105,7 +105,7 @@ func newTxAppender( } ia := newInvokeApplier(state, sc, txHandler, stor, settings, blockDiffer, diffStorInvoke, diffApplier) ethKindResolver := proto.NewEthereumTransactionKindResolver(state, settings.AddressSchemeCharacter) - finalizer := newFinalizer(stor.generators, stor.finality) + finalizer := newFinalizer(stor.generators, stor.finality, stor.balances) return &txAppender{ sc: sc, ia: ia, diff --git a/pkg/state/balances.go b/pkg/state/balances.go index 5c9059c28b..74c9a8a007 100644 --- a/pkg/state/balances.go +++ b/pkg/state/balances.go @@ -1013,3 +1013,46 @@ func (s *balances) reset() { s.leaseHashesState = make(map[proto.BlockID]*stateForHashes) s.leaseHashes = make(map[proto.BlockID]crypto.Digest) } + +// burnDeposit reduces the balance of an account by Deposit amount. The Deposit part of balance profile is not affected. +func (s *balances) burnDeposit(addr proto.AddressID, blockID proto.BlockID) error { + balance, err := s.newestWavesBalance(addr) + if err != nil { + return fmt.Errorf("failed to burn deposit: %w", err) + } + balance.Balance, err = common.SubInt(balance.Balance, Deposit) + if err != nil { + return fmt.Errorf("failed to burn deposit: %w", err) + } + v := wavesValue{ + profile: balance, + leaseChange: false, + balanceChange: true, + } + if sbErr := s.setWavesBalance(addr, v, blockID); sbErr != nil { + return fmt.Errorf("failed to burn deposit: %w", sbErr) + } + return nil +} + +func (s *balances) resetDeposit(addr proto.AddressID, blockID proto.BlockID) (uint64, uint64, error) { + balance, err := s.newestWavesBalance(addr) + if err != nil { + return 0, 0, fmt.Errorf("failed to reset deposit: %w", err) + } + before := balance.Deposit + after, err := common.SubInt(before, Deposit) + if err != nil { + return 0, 0, fmt.Errorf("failed to reset deposit: %w", err) + } + balance.Deposit = after + v := wavesValue{ + profile: balance, + leaseChange: false, + balanceChange: false, + } + if sbErr := s.setWavesBalance(addr, v, blockID); sbErr != nil { + return 0, 0, fmt.Errorf("failed to reset deposit: %w", sbErr) + } + return before, after, nil +} diff --git a/pkg/state/balances_test.go b/pkg/state/balances_test.go index 6c4501e4cb..d56e19f590 100644 --- a/pkg/state/balances_test.go +++ b/pkg/state/balances_test.go @@ -452,6 +452,37 @@ func TestBalances(t *testing.T) { } } +func TestBurnDeposit(t *testing.T) { + const waves = 1_0000_0000 + to := createBalances(t) + to.stor.addBlock(t, blockID0) + to.stor.addBlock(t, blockID1) + wavesTests := []struct { + addr string + profile balanceProfile + err string + }{ + {addr0, balanceProfile{200 * waves, 0, 0, 100 * waves}, ""}, + {addr1, balanceProfile{1100 * waves, 0, 0, 100 * waves}, ""}, + {addr1, balanceProfile{100 * waves, 0, 0, 100 * waves}, ""}, + {addr1, balanceProfile{10 * waves, 0, 0, 100 * waves}, + "failed to burn deposit: sub: integer overflow/underflow"}, + } + for _, tc := range wavesTests { + addr, err := proto.NewAddressFromString(tc.addr) + require.NoError(t, err) + err = to.balances.setWavesBalance(addr.ID(), newWavesValueFromProfile(tc.profile), blockID0) + require.NoError(t, err) + to.stor.flush(t) + err = to.balances.burnDeposit(addr.ID(), blockID1) + if tc.err == "" { + require.NoError(t, err) + } else { + require.EqualError(t, err, tc.err) + } + } +} + func TestNftList(t *testing.T) { to := createBalances(t) diff --git a/pkg/state/commitments.go b/pkg/state/commitments.go index 49bbdf1448..33df8c7d90 100644 --- a/pkg/state/commitments.go +++ b/pkg/state/commitments.go @@ -16,8 +16,9 @@ import ( // commitmentItem represents a single commitment made by a block generator. // It links the generator's Waves public key to its corresponding BLS endorser public key. type commitmentItem struct { - GeneratorPK crypto.PublicKey `cbor:"0,keyasint,omitempty"` - EndorserPK bls.PublicKey `cbor:"1,keyasint,omitempty"` + GeneratorPK crypto.PublicKey `cbor:"0,keyasint,omitempty"` + EndorserPK bls.PublicKey `cbor:"1,keyasint,omitempty"` + TransactionID crypto.Digest `cbor:"2,keyasint,omitempty"` } // commitmentsRecord holds all generator commitments for a specific generation period. @@ -25,10 +26,11 @@ type commitmentsRecord struct { Commitments []commitmentItem `cbor:"0,keyasint,omitempty"` } -func (cr *commitmentsRecord) append(generatorPK crypto.PublicKey, endorserPK bls.PublicKey) { +func (cr *commitmentsRecord) append(generatorPK crypto.PublicKey, endorserPK bls.PublicKey, txID crypto.Digest) { cr.Commitments = append(cr.Commitments, commitmentItem{ - GeneratorPK: generatorPK, - EndorserPK: endorserPK, + GeneratorPK: generatorPK, + EndorserPK: endorserPK, + TransactionID: txID, }) } func (cr *commitmentsRecord) marshalBinary() ([]byte, error) { return cbor.Marshal(cr) } @@ -88,7 +90,8 @@ func newCommitments(hs *historyStorage, calcHashes bool) *commitments { } func (c *commitments) store( - periodStart uint32, generatorPK crypto.PublicKey, endorserPK bls.PublicKey, blockID proto.BlockID, + periodStart uint32, generatorPK crypto.PublicKey, endorserPK bls.PublicKey, txID crypto.Digest, + blockID proto.BlockID, ) error { key := commitmentKey{periodStart: periodStart} keyBytes := key.bytes() @@ -102,7 +105,7 @@ func (c *commitments) store( return fmt.Errorf("failed to unmarshal commitments record: %w", umErr) } } - rec.append(generatorPK, endorserPK) + rec.append(generatorPK, endorserPK, txID) newData, mErr := rec.marshalBinary() if mErr != nil { return fmt.Errorf("failed to marshal commitments record: %w", mErr) diff --git a/pkg/state/commitments_internal_test.go b/pkg/state/commitments_internal_test.go index 07ef693b9b..44ce1e388d 100644 --- a/pkg/state/commitments_internal_test.go +++ b/pkg/state/commitments_internal_test.go @@ -88,19 +88,23 @@ func TestCommitments_Exists(t *testing.T) { cms := generateCommitments(t, test.n+1) for j := range test.n { blockID := generateRandomBlockID(t) + txID := generateRandomDigest(t) to.addBlock(t, blockID) - err := to.entities.commitments.store(test.periodStart, cms[j].GeneratorPK, cms[j].EndorserPK, blockID) + err := to.entities.commitments.store(test.periodStart, cms[j].GeneratorPK, cms[j].EndorserPK, txID, + blockID) require.NoError(t, err) // Check that all added commitments exist. for k := range j { - ok, eErr := to.entities.commitments.newestExists(test.periodStart, cms[k].GeneratorPK, cms[k].EndorserPK) + ok, eErr := to.entities.commitments.newestExists(test.periodStart, cms[k].GeneratorPK, + cms[k].EndorserPK) require.NoError(t, eErr) assert.True(t, ok) } // Check that non-existing commitment does not exist. - ok, err := to.entities.commitments.newestExists(test.periodStart, cms[test.n].GeneratorPK, cms[test.n].EndorserPK) + ok, err := to.entities.commitments.newestExists(test.periodStart, cms[test.n].GeneratorPK, + cms[test.n].EndorserPK) require.NoError(t, err) assert.False(t, ok) @@ -114,7 +118,8 @@ func TestCommitments_Exists(t *testing.T) { } // Check that non-existing commitment does not exist after flush. - ok, err = to.entities.commitments.exists(test.periodStart, cms[test.n].GeneratorPK, cms[test.n].EndorserPK) + ok, err = to.entities.commitments.exists(test.periodStart, cms[test.n].GeneratorPK, + cms[test.n].EndorserPK) require.NoError(t, err) assert.False(t, ok) } @@ -137,8 +142,10 @@ func TestCommitmentsBatch(t *testing.T) { cms := generateCommitments(t, test.n) for j := range test.n { blockID := generateRandomBlockID(t) + txID := generateRandomDigest(t) to.addBlock(t, blockID) - err := to.entities.commitments.store(test.periodStart, cms[j].GeneratorPK, cms[j].EndorserPK, blockID) + err := to.entities.commitments.store(test.periodStart, cms[j].GeneratorPK, cms[j].EndorserPK, txID, + blockID) require.NoError(t, err) } // Unflushed size check. @@ -159,8 +166,9 @@ func TestRepeatedUsageOfBLSKey(t *testing.T) { periodStart := uint32(1_000_000) cms := generateCommitments(t, 2) bID1 := generateRandomBlockID(t) + txID1 := generateRandomDigest(t) to.addBlock(t, bID1) - err := to.entities.commitments.store(periodStart, cms[0].GeneratorPK, cms[0].EndorserPK, bID1) + err := to.entities.commitments.store(periodStart, cms[0].GeneratorPK, cms[0].EndorserPK, txID1, bID1) require.NoError(t, err) // Check that the commitment exist. @@ -194,9 +202,11 @@ func generateCommitments(t testing.TB, n int) []commitmentItem { require.NoError(t, err) bpk, err := bsk.PublicKey() require.NoError(t, err) + txID := generateRandomDigest(t) r[i] = commitmentItem{ - GeneratorPK: wpk, - EndorserPK: bpk, + GeneratorPK: wpk, + EndorserPK: bpk, + TransactionID: txID, } } return r @@ -208,3 +218,10 @@ func generateRandomBlockID(t testing.TB) proto.BlockID { require.NoError(t, err) return proto.NewBlockIDFromSignature(sig) } + +func generateRandomDigest(t testing.TB) crypto.Digest { + var d crypto.Digest + _, err := rand.Read(d[:]) + require.NoError(t, err) + return d +} diff --git a/pkg/state/finalization.go b/pkg/state/finalization.go index 6b521abe01..27fd7dd7bf 100644 --- a/pkg/state/finalization.go +++ b/pkg/state/finalization.go @@ -158,6 +158,7 @@ func (f *finality) buildLocalEndorsementMessage( slog.Debug("Local finalization state", slog.Uint64("finalizedHeight", finalizedHeight), slog.String("finalizedBlockID", finalizedBlockID.String()), + slog.String("parentID", parentID.String()), ) fh, err := safecast.Convert[uint32](finalizedHeight) if err != nil { @@ -170,6 +171,27 @@ func (f *finality) buildLocalEndorsementMessage( }, nil } +func (f *finality) buildRemoteEndorsementMessage( + finalizedBlockHeight proto.Height, parentID proto.BlockID, +) (proto.EndorsementCryptoMessage, error) { + finalizedBlockID, err := f.rw.newestBlockIDByHeight(finalizedBlockHeight) + if err != nil { + return proto.EndorsementCryptoMessage{}, fmt.Errorf("failed to build remote endorsement message: %w", err) + } + slog.Debug("Remote finalization state", slog.Uint64("finalizedHeight", finalizedBlockHeight), + slog.String("finalizedBlockID", finalizedBlockID.String()), + slog.String("endorsedBlockID", parentID.String())) + fh, err := safecast.Convert[uint32](finalizedBlockHeight) + if err != nil { + return proto.EndorsementCryptoMessage{}, fmt.Errorf("failed to build remote endorsement message: %w", err) + } + return proto.EndorsementCryptoMessage{ + FinalizedBlockID: finalizedBlockID, + FinalizedBlockHeight: fh, + EndorsedBlockID: parentID, + }, nil +} + func calculateLastFinalizedHeight(height proto.Height) proto.Height { const genesisHeight uint64 = 1 const maxRollbackDeltaHeight uint64 = 100 diff --git a/pkg/state/finalizer.go b/pkg/state/finalizer.go index ff9710662b..d74a594457 100644 --- a/pkg/state/finalizer.go +++ b/pkg/state/finalizer.go @@ -9,28 +9,37 @@ import ( "github.com/wavesplatform/gowaves/pkg/proto" ) +type depositResetter interface { + resetDeposit(addr proto.AddressID, nextBlockID proto.BlockID) (uint64, uint64, error) +} + type finalizer struct { - generators *generators + generators *generatorsStorage finality *finality + resetter depositResetter } -func newFinalizer(generators *generators, finality *finality) *finalizer { +func newFinalizer(generators *generatorsStorage, finality *finality, resetter depositResetter) *finalizer { return &finalizer{ generators: generators, finality: finality, + resetter: resetter, } } -func (f *finalizer) checkBlockFinalization(voting proto.FinalizationVoting) error { - if l := len(voting.ConflictEndorsements); l > f.generators.size() { - return fmt.Errorf("conflicting endorsements count %d exceeds generator set size %d", - l, f.generators.size()) +func (f *finalizer) checkBlockFinalization(voting proto.FinalizationVoting, height proto.Height) error { + if voting.FinalizedBlockHeight >= height { + return fmt.Errorf("invalid finalization voting: finalized block height %d should be less than block height %d", + voting.FinalizedBlockHeight, height) } - if l := len(voting.EndorserIndexes); l > f.generators.size() { - return fmt.Errorf("endorsements count %d exceeds generator set size %d", - l, f.generators.size()) + gs, err := f.generators.generators(height) + if err != nil { + if errors.Is(err, ErrNoGeneratorsSet) { + return voting.CheckSizes(0) + } + return fmt.Errorf("failed to check block finalization: %w", err) } - return nil + return voting.CheckSizes(gs.size()) } // processBlockFinalization performs state updates required for block finalization @@ -43,44 +52,43 @@ func (f *finalizer) processBlockFinalization( block *proto.BlockHeader, height proto.Height, ) error { - if err := f.checkBlockFinalization(finalizationVoting); err != nil { + if err := f.checkBlockFinalization(finalizationVoting, height); err != nil { return err } blockID := block.BlockID() - if err := f.processConflictingEndorsements(finalizationVoting.ConflictEndorsements, blockID); err != nil { + if err := f.processConflictingEndorsements(finalizationVoting.ConflictEndorsements, height, blockID); err != nil { return err } // Check that other endorsers are valid to endorse the parent block. var endorsersBalance uint64 = 0 - bg, err := f.generators.blockGenerator() + bgi, bg, err := f.generators.blockGenerator(height) if err != nil { return fmt.Errorf("failed to get block generator: %w", err) } - blockGeneratorIndex := bg.index pks := make([]bls.PublicKey, 0, len(finalizationVoting.EndorserIndexes)) for _, ei := range finalizationVoting.EndorserIndexes { - g, gErr := f.generators.generator(ei) + g, gErr := f.generators.generator(ei, height) if gErr != nil { return fmt.Errorf("failed to get endorser by index %d: %w", ei, gErr) } - if g.ban { - return fmt.Errorf("banned generator '%s' finalization voting found", g.Address()) + if g.Ban { + return fmt.Errorf("banned generator '%s' finalization voting found", g.Address.String()) } - if blockGeneratorIndex == ei { - return fmt.Errorf("block generator '%s' found in finalization voting", bg.Address()) + if bgi == ei { + return fmt.Errorf("block generator with index %d found in finalization voting", bgi) } - balance := g.GenerationBalance() + balance := g.Balance if balance == 0 { return fmt.Errorf("generator '%s' with insufficient generation balance found in finalization voting", - g.Address()) + g.Address.String()) } - pks = append(pks, g.BLSPublicKey()) + pks = append(pks, g.BLSPublicKey) endorsersBalance += balance } // Add block generator's balance to endorsers balance. - endorsersBalance += bg.GenerationBalance() // Balance of block generator already checked. + endorsersBalance += bg.Balance // Balance of block generator already checked. // Check aggregate signature. - msg, err := f.finality.buildLocalEndorsementMessage(height, block.Parent) + msg, err := f.finality.buildRemoteEndorsementMessage(finalizationVoting.FinalizedBlockHeight, block.Parent) if err != nil { return err } @@ -94,10 +102,14 @@ func (f *finalizer) processBlockFinalization( // A block is considered finalized if the total endorsers' balance is at least 2/3 of the committed // generators' total balance. - if 3*endorsersBalance >= 2*f.generators.totalGenerationBalance() { + totalGenerationBalance, err := f.generators.totalGenerationBalance(height) + if err != nil { + return err + } + if 3*endorsersBalance >= 2*totalGenerationBalance { finalizedHeight := height - 1 - slog.Info("Block finalization achieved", slog.Uint64("finalizedHeight", finalizedHeight), - slog.Uint64("blockHeigh", height), slog.String("blockID", blockID.String())) + slog.Debug("Block finalization achieved", slog.Uint64("finalizedHeight", finalizedHeight), + slog.Uint64("blockHeight", height), slog.String("blockID", blockID.String())) if fErr := f.finality.updatePendingFinalization(finalizedHeight, blockID); fErr != nil { return fmt.Errorf("failed to update pending finalization: %w", fErr) } @@ -106,7 +118,7 @@ func (f *finalizer) processBlockFinalization( } func (f *finalizer) processConflictingEndorsements( - conflictingEndorsements []proto.BlockEndorsement, blockID proto.BlockID, + conflictingEndorsements []proto.BlockEndorsement, blockHeight proto.Height, blockID proto.BlockID, ) error { for _, ce := range conflictingEndorsements { // Check the signature of conflicting endorsement. @@ -115,12 +127,12 @@ func (f *finalizer) processConflictingEndorsements( return fmt.Errorf("failed to build crypto message for conflicting endorsement with index %d: %w", ce.EndorserIndex, err) } - gi, err := f.generators.generator(ce.EndorserIndex) + gi, err := f.generators.generator(ce.EndorserIndex, blockHeight) if err != nil { return fmt.Errorf("failed to get generator for conflicting endorsement with index %d: %w", ce.EndorserIndex, err) } - valid, err := bls.Verify(gi.BLSPublicKey(), cmb, ce.Signature) + valid, err := bls.Verify(gi.BLSPublicKey, cmb, ce.Signature) if err != nil { return fmt.Errorf("failed to verify signature of conflicting endorsement with index %d: %w", ce.EndorserIndex, err) @@ -130,10 +142,19 @@ func (f *finalizer) processConflictingEndorsements( ce.EndorserIndex) } // Ban generator of conflicting endorsement. - if bErr := f.generators.banGenerator(ce.EndorserIndex, blockID); bErr != nil { + if bErr := f.generators.banGenerator(ce.EndorserIndex, blockHeight, blockID); bErr != nil { return fmt.Errorf("failed to ban generator of conflicting endorsement with index %d: %w", ce.EndorserIndex, bErr) } + before, after, err := f.resetter.resetDeposit(gi.Address.ID(), blockID) + if err != nil { + return fmt.Errorf("failed to reset deposit of generator of conflicting endorsement with index %d: %w", + ce.EndorserIndex, err) + } + slog.Debug("Conflicting endorsement processed: generator banned and deposit reset", + slog.Uint64("blockHeight", blockHeight), slog.String("blockID", blockID.String()), + slog.String("generator", gi.Address.String()), slog.Uint64("depositBefore", before), + slog.Uint64("depositAfter", after)) } return nil } diff --git a/pkg/state/generators.go b/pkg/state/generators.go index 87c58aa8f1..a4cea94d6c 100644 --- a/pkg/state/generators.go +++ b/pkg/state/generators.go @@ -1,15 +1,16 @@ package state import ( - "bytes" "encoding/binary" + "errors" "fmt" "io" - "slices" + "log/slog" + "strconv" "strings" + "github.com/ccoveille/go-safecast/v2" "github.com/fxamacker/cbor/v2" - "github.com/pkg/errors" "github.com/wavesplatform/gowaves/pkg/crypto" "github.com/wavesplatform/gowaves/pkg/crypto/bls" @@ -17,10 +18,11 @@ import ( "github.com/wavesplatform/gowaves/pkg/settings" ) -// generationBalanceProvider is an interface that abstracts the retrieval of generating balances for addresses at -// specific heights. Usually, for generating balance retrieval the blockchain height is used. -type generationBalanceProvider interface { +var ErrNoGeneratorsSet = errors.New("no generators set found") + +type generationBalanceManager interface { newestGeneratingBalance(proto.AddressID, proto.Height) (uint64, error) + burnDeposit(proto.AddressID, proto.BlockID) error } // commitmentsProvider is an interface that abstracts the retrieval of generator commitments for a given @@ -30,66 +32,129 @@ type commitmentsProvider interface { } type GeneratorInfo struct { - index uint32 - address proto.WavesAddress - pk crypto.PublicKey - blsPK bls.PublicKey - balance uint64 - ban bool - threshold uint64 + Index uint32 `json:"-"` + Address proto.WavesAddress `json:"address"` + PublicKey crypto.PublicKey `json:"-"` + BLSPublicKey bls.PublicKey `json:"-"` + Balance uint64 `json:"balance"` + Ban bool `json:"-"` + TransactionID crypto.Digest `json:"transactionId"` +} + +func buildGeneratorInfo( + index uint32, commitments []commitmentItem, generators []generator, scheme proto.Scheme, +) (GeneratorInfo, error) { + if cs, gs := len(commitments), len(generators); cs != gs { + return GeneratorInfo{}, fmt.Errorf("number of commitments %d does not match number of generators %d", + cs, gs) + } + if s := len(commitments); int(index) >= s { + return GeneratorInfo{}, fmt.Errorf("invalid generator index %d for commitments of size %d", index, s) + } + c := commitments[index] + g := generators[index] + a, aErr := g.AddressID.ToWavesAddress(scheme) + if aErr != nil { + return GeneratorInfo{}, fmt.Errorf("failed to convert address ID to Waves address for generator %d: %w", + index, aErr) + } + bal := g.Balance + banned := g.BanHeight != 0 + if banned { + bal = 0 + } + gi := GeneratorInfo{ + Index: index, + Address: a, + PublicKey: c.GeneratorPK, + BLSPublicKey: c.EndorserPK, + Balance: bal, + Ban: banned, + TransactionID: c.TransactionID, + } + return gi, nil +} + +func buildInfos(commitments []commitmentItem, generators []generator, scheme proto.Scheme) ([]GeneratorInfo, error) { + if cs, gs := len(commitments), len(generators); cs != gs { + return nil, fmt.Errorf("number of commitments %d does not match number of generators %d", cs, gs) + } + res := make([]GeneratorInfo, len(commitments)) + for i := range commitments { + idx, err := safecast.Convert[uint32](i) + if err != nil { + return nil, fmt.Errorf("failed to convert generator index %d: %w", i, err) + } + gi, err := buildGeneratorInfo(idx, commitments, generators, scheme) + if err != nil { + return nil, err + } + res[i] = gi + } + return res, nil } -func (g *GeneratorInfo) Index() uint32 { - return g.index +type generator struct { + Balance uint64 `cbor:"0,keyasint,omitempty"` + BanHeight uint64 `cbor:"1,keyasint,omitempty"` + AddressID proto.AddressID `cbor:"2,keyasint,omitempty"` } -func (g *GeneratorInfo) GenerationBalance() uint64 { - if g.ban { - return 0 +func (g *generator) string(scheme proto.Scheme) string { + var as string + if a, err := g.AddressID.ToWavesAddress(scheme); err != nil { + as = "⚠" + } else { + as = a.String() } - if g.balance < g.threshold { // If the balance of generator is less than minimal generation balance, return 0. - return 0 + bs := "✓" + if g.BanHeight != 0 { + bs = fmt.Sprintf("✗%d", g.BanHeight) } - return g.balance -} - -func (g *GeneratorInfo) BLSPublicKey() bls.PublicKey { - return g.blsPK -} - -func (g *GeneratorInfo) Address() proto.WavesAddress { - return g.address + return fmt.Sprintf("%s%s(¤%d)", as, bs, g.Balance) } -// bannedGeneratorsRecord is a structure used for CBOR serialization of banned generator indexes. -// It has public fields to allow encoding/decoding with the specified CBOR tags. -type bannedGeneratorsRecord struct { - Indexes []uint32 `cbor:"0,keyasint,omitempty"` +type generatorsRecord struct { + Generators []generator `cbor:"0,keyasint,omitempty"` + BlockGeneratorIndex int32 `cbor:"1,keyasint,omitempty"` + PeriodStart uint32 `cbor:"2,keyasint,omitempty"` } -func (r *bannedGeneratorsRecord) appendIndex(index uint32) error { - if slices.Contains(r.Indexes, index) { - return errors.Errorf("index %d is already present in the record", index) +// banGenerator updates existing generator setting the height at which it was banned. +func (r *generatorsRecord) banGenerator(index uint32, height proto.Height) error { + if s := len(r.Generators); int(index) >= s { + return fmt.Errorf("generator index %d is out of bounds for the generator set of size %d", index, s) + } + g := r.Generators[index] + if g.BanHeight != 0 && g.BanHeight <= height { + return fmt.Errorf("generator with index %d is already banned at height %d", index, g.BanHeight) } - r.Indexes = append(r.Indexes, index) + r.Generators[index].BanHeight = height return nil } -func (r *bannedGeneratorsRecord) marshalBinary() ([]byte, error) { return cbor.Marshal(r) } +func (r *generatorsRecord) marshalBinary() ([]byte, error) { return cbor.Marshal(r) } -func (r *bannedGeneratorsRecord) unmarshalBinary(data []byte) error { return cbor.Unmarshal(data, r) } +func (r *generatorsRecord) unmarshalBinary(data []byte) error { return cbor.Unmarshal(data, r) } -// bannedGeneratorsKey is a structure used to generate a unique key for storing banned generators in the -// history storage. -type bannedGeneratorsKey struct { - periodStart uint32 -} +func (r *generatorsRecord) size() int { return len(r.Generators) } -func (k *bannedGeneratorsKey) bytes() []byte { - buf := make([]byte, 1+uint32Size) - buf[0] = bannedGeneratorsKeyPrefix - binary.BigEndian.PutUint32(buf[1:], k.periodStart) - return buf +func (r *generatorsRecord) string(scheme proto.Scheme) string { + sb := strings.Builder{} + sb.WriteRune('▶') + sb.WriteString(strconv.Itoa(int(r.PeriodStart))) + sb.WriteRune('(') + sb.WriteString(strconv.Itoa(int(r.BlockGeneratorIndex))) + sb.WriteRune(')') + sb.WriteRune('[') + for i, gen := range r.Generators { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(gen.string(scheme)) + } + sb.WriteRune(']') + return sb.String() } type generatorsBalancesRecordForStateHashes struct { @@ -133,36 +198,31 @@ func (r *generatorsBalancesRecordForStateHashes) less(other stateComponent) bool } // ByBLSPublicKey returns a lookup function that checks if a generator's BLS public key matches the provided one. -func ByBLSPublicKey(pk bls.PublicKey) func(GeneratorInfo) bool { +func ByBLSPublicKey(pk bls.PublicKey) func(info GeneratorInfo) bool { return func(info GeneratorInfo) bool { - return bytes.Equal(info.blsPK.Bytes(), pk.Bytes()) + return info.BLSPublicKey == pk } } // ByIndex returns a lookup function that checks if a generator's index matches the provided one. func ByIndex(index uint32) func(GeneratorInfo) bool { return func(info GeneratorInfo) bool { - return info.index == index + return info.Index == index } } -// generators manages the set of generators for the current block. +// generatorsStorage manages the set of generators for the current block. // The generators storage entity itself lifetime can be larger than a single block, but the generator set is expected // to be initialized and used within the context of a single block processing. The underlying history storage and // legacy hasher are expected to be shared across multiple blocks. -type generators struct { +type generatorsStorage struct { hs *historyStorage fs featuresState - balances generationBalanceProvider + balances generationBalanceManager commitments commitmentsProvider settings *settings.BlockchainSettings - set []GeneratorInfo - generationPeriodStart uint32 - blockGeneratorIndex int // Current block generator info. - blockHeight uint64 // Current block height. - calculateHashes bool hasher *stateHasher } @@ -170,67 +230,118 @@ type generators struct { func newGenerators( hs *historyStorage, fs featuresState, - balances generationBalanceProvider, + balances generationBalanceManager, commitments commitmentsProvider, sets *settings.BlockchainSettings, calcHashes bool, -) *generators { - return &generators{ +) *generatorsStorage { + return &generatorsStorage{ hs: hs, fs: fs, balances: balances, commitments: commitments, settings: sets, - set: make([]GeneratorInfo, 0), calculateHashes: calcHashes, hasher: newStateHasher(), } } -func (g *generators) wipe() { - g.set = nil - g.generationPeriodStart = 0 - g.blockGeneratorIndex = -1 - g.blockHeight = 0 -} - // initialize populates the generator set based on the provided commitments and balances. // This method should be called upon block header processing. // Parameters: // // blockchainHeight - height of the state (height of the last applied block), // blockID - ID of the applied block. -func (g *generators) initialize( - blockchainHeight proto.Height, blockID proto.BlockID, generator crypto.PublicKey, ts uint64, +func (g *generatorsStorage) initialize( + blockchainHeight proto.Height, blockID proto.BlockID, genPK crypto.PublicKey, ts uint64, ) error { - g.wipe() activationHeight, err := g.fs.newestActivationHeight(int16(settings.DeterministicFinality)) if err != nil { if isNotFoundInHistoryOrDBErr(err) { // DeterministicFinality feature is not approved or activated. - return nil + return nil // No need to calculate generators state hashes here. } return fmt.Errorf("failed to get activation height for Deterministic Finality feature: %w", err) } - g.blockHeight = blockchainHeight + 1 - g.generationPeriodStart, err = CurrentGenerationPeriodStart(activationHeight, g.blockHeight, - g.settings.GenerationPeriod) + blockHeight := blockchainHeight + 1 + periodStart, err := CurrentGenerationPeriodStart(activationHeight, blockHeight, g.settings.GenerationPeriod) if err != nil { return fmt.Errorf("failed to calculate current generation period start: %w", err) } - cms, err := g.commitments.newestCommitments(g.generationPeriodStart) + cms, err := g.commitments.newestCommitments(periodStart) if err != nil { return fmt.Errorf("failed to initialize generators set: %w", err) } - // Load saved bans of generators in the current generation period. - bans, err := g.bans(g.generationPeriodStart) - if err != nil { - return fmt.Errorf("failed to retrieve banned generators for the current generation period: %w", err) + threshold := g.fs.minimalGeneratingBalanceAtHeight(blockHeight, ts) + // Load generators on the previous height to extract banned generators or copy generator set. + pg, err := g.generators(blockchainHeight) + if err != nil && !errors.Is(err, ErrNoGeneratorsSet) { + return fmt.Errorf("failed to retrieve previous generators: %w", err) + } + noCommitments := len(cms) == 0 + noPrevGenerators := errors.Is(err, ErrNoGeneratorsSet) + switch { + case noCommitments && noPrevGenerators: + // No commitments for the current period and no generators from the previous block, nothing to do. + slog.Debug("No commitments, no previous generators found", slog.Uint64("height", blockHeight)) + return nil + + case noCommitments && pg.PeriodStart != periodStart: + // No commitments for the current generation period, but if there is a previous generators set with + // the different generation period, we have to punish conflicting endorsements on the last block of + // previous generation period. + slog.Debug("No commitments, but previous generators found", slog.Uint64("height", blockHeight)) + return g.punish(pg, blockchainHeight, blockID) + + case noCommitments && pg.PeriodStart == periodStart: + // This situation is impossible, no commitments for the current period, but there is a previous generators + // record with the same start of generation period. + return fmt.Errorf("impossible state, generation record for height %d exist, "+ + "but commitments for the same period are empty", blockHeight) + + case noPrevGenerators: + // There are commitments for the current generation period, but the previous generators list is empty, + // this means that we are at the start of new generation period, after a generation period without commitments. + // We should initalize a new generator set. + slog.Debug("Initializing new generators set, no previous generators found", slog.Uint64("height", blockHeight)) + return g.initializeNewGeneratorsSetFromCommitments(cms, periodStart, threshold, genPK, blockHeight, + blockchainHeight, blockID) + + case pg.PeriodStart == periodStart: + // The generation period did not change, punish conflicting endorsements then copy the previous generators set + // and update their balances. + if pErr := g.punish(pg, blockchainHeight, blockID); pErr != nil { + return fmt.Errorf("failed to punish conflicting endorsements of height %d: %w", blockchainHeight, pErr) + } + return g.copyGeneratorsSetAndUpdateBalances(pg, cms, threshold, genPK, blockHeight, blockchainHeight, blockID) + + default: + // Here we are crossing the edge of generation period, new commitments and previous generator set with a + // different generation period start. First of all, we should punish conflicting endorsements for the last + // block of previous generation period, and then create a new generator set. + if pErr := g.punish(pg, blockHeight, blockID); pErr != nil { + return fmt.Errorf("failed to punish conflicting endorsements of height %d: %w", blockchainHeight, pErr) + } + return g.initializeNewGeneratorsSetFromCommitments(cms, periodStart, threshold, genPK, blockHeight, + blockchainHeight, blockID) } +} + +func (g *generatorsStorage) initializeNewGeneratorsSetFromCommitments( + commitments []commitmentItem, start uint32, threshold uint64, generatorPK crypto.PublicKey, + blockHeight, blockchainHeight proto.Height, + blockID proto.BlockID) error { + rec := generatorsRecord{ + Generators: make([]generator, 0, len(commitments)), + BlockGeneratorIndex: -1, + PeriodStart: start, + } + // Calculate minimal generation balance for the current height and timestamp. - threshold := g.fs.minimalGeneratingBalanceAtHeight(g.blockHeight, ts) - g.set = make([]GeneratorInfo, 0, len(cms)) - generatorsBalancesLSHRecord := newGeneratorsBalancesRecordForStateHashes(len(cms)) - for i, cm := range cms { + var generatorsBalancesLSHRecord *generatorsBalancesRecordForStateHashes + if g.calculateHashes { + generatorsBalancesLSHRecord = newGeneratorsBalancesRecordForStateHashes(len(commitments)) + } + for i, cm := range commitments { a, aErr := proto.NewAddressFromPublicKey(g.settings.AddressSchemeCharacter, cm.GeneratorPK) if aErr != nil { return fmt.Errorf("failed to derive address from generator public key at index %d: %w", i, aErr) @@ -242,149 +353,285 @@ func (g *generators) initialize( return fmt.Errorf("failed to get balance for generator at index %d by address '%s': %w", i, a.String(), bErr) } - idx := uint32(i) - gi := GeneratorInfo{ - index: idx, - address: a, - pk: cm.GeneratorPK, - blsPK: cm.EndorserPK, - balance: b, - ban: slices.Contains(bans, idx), - threshold: threshold, + if b < threshold { + b = 0 // Set generation balances less than threshold to zero, so they are not eligible for generation. + } + if generatorPK == cm.GeneratorPK { + // The block generator found. + idx, scErr := safecast.Convert[int32](i) + if scErr != nil { + return fmt.Errorf("failed to convert generator index: %w", scErr) + } + rec.BlockGeneratorIndex = idx } - g.set = append(g.set, gi) - if cm.GeneratorPK == generator { - g.blockGeneratorIndex = i // Save index of the current block generator. + gen := generator{ + Balance: b, + BanHeight: 0, + AddressID: a.ID(), } - if g.calculateHashes { + rec.Generators = append(rec.Generators, gen) + if g.calculateHashes && b > 0 && generatorsBalancesLSHRecord != nil { generatorsBalancesLSHRecord.append(b) } } - if len(cms) > 0 && g.blockGeneratorIndex == -1 { - // The block generator was not initialized, which means it is not part of the committed generators. + return g.finishGeneratorsInitialization(rec, generatorsBalancesLSHRecord, generatorPK, blockHeight, + blockchainHeight, blockID, "New generators set initialized") +} + +func (g *generatorsStorage) finishGeneratorsInitialization( + rec generatorsRecord, shRec stateComponent, + generatorPK crypto.PublicKey, blockHeight, blockchainHeight proto.Height, blockID proto.BlockID, + logMessage string, +) error { + if rec.BlockGeneratorIndex < 0 { + // The block generator index was not initialized, which means it is not part of the committed generators. // This serves as an additional safety check, since the generator has already been validated by its // generation balance. - return fmt.Errorf("block generator with public key '%s' is not in the committed generators set", - generator.String()) + return fmt.Errorf( + "block generator with public key '%s' is not in the commitments for the current generation period", + generatorPK.String()) } + if sErr := g.saveGeneratorsRecord(rec, blockHeight, blockID); sErr != nil { + return fmt.Errorf("failed to save block generator record: %w", sErr) + } + slog.Debug(logMessage, slog.String("generators", rec.string(g.settings.AddressSchemeCharacter)), + slog.Uint64("blockHeight", blockHeight), slog.String("blockID", blockID.String()), + slog.Uint64("blockchainHeight", blockchainHeight)) + return g.pushLegacyStateHashRecord(shRec, blockHeight, blockID) +} + +func (g *generatorsStorage) copyGeneratorsSetAndUpdateBalances( + pg *generatorsRecord, commitments []commitmentItem, threshold uint64, generatorPK crypto.PublicKey, + blockHeight, blockchainHeight proto.Height, + blockID proto.BlockID, +) error { + if gs, cs := len(pg.Generators), len(commitments); gs != cs { + return fmt.Errorf("invalid previous generators size %d while commitments size if %d", gs, cs) + } + rec := generatorsRecord{ + Generators: make([]generator, 0, len(pg.Generators)), + BlockGeneratorIndex: -1, + PeriodStart: pg.PeriodStart, + } + var generatorsBalancesLSHRecord *generatorsBalancesRecordForStateHashes if g.calculateHashes { - key := bannedGeneratorsKey{periodStart: g.generationPeriodStart} - if pErr := g.hasher.push(string(key.bytes()), generatorsBalancesLSHRecord, blockID); pErr != nil { - return fmt.Errorf("failed to hash generators balances record: %w", pErr) + generatorsBalancesLSHRecord = newGeneratorsBalancesRecordForStateHashes(len(pg.Generators)) + } + for i, gen := range pg.Generators { + ng := generator{ + Balance: 0, + BanHeight: gen.BanHeight, + AddressID: gen.AddressID, + } + if gen.BanHeight == 0 { // The generator is not banned, query the balance. + // The initialization happens at the very beginning of the block, so the generation balance is + // queried at the height of the last applied block (blockchainHeight). + bal, bErr := g.balances.newestGeneratingBalance(gen.AddressID, blockchainHeight) + if bErr != nil { + return fmt.Errorf("failed to get balance for generator at index %d: %w", i, bErr) + } + if bal < threshold { + bal = 0 // Set to zero, generators with insufficient balance excluded from generators set. + } + ng.Balance = bal + } + if generatorPK == commitments[i].GeneratorPK { + // The block generator found. + idx, scErr := safecast.Convert[int32](i) + if scErr != nil { + return fmt.Errorf("failed to convert generator index: %w", scErr) + } + rec.BlockGeneratorIndex = idx + } + rec.Generators = append(rec.Generators, ng) + if g.calculateHashes && ng.Balance > 0 && generatorsBalancesLSHRecord != nil { + generatorsBalancesLSHRecord.append(ng.Balance) + } + } + return g.finishGeneratorsInitialization(rec, generatorsBalancesLSHRecord, generatorPK, blockHeight, + blockchainHeight, blockID, "Generators set updated") +} + +func (g *generatorsStorage) punish(pg *generatorsRecord, blockchainHeight proto.Height, blockID proto.BlockID) error { + if pg == nil { // Sanity check. + return nil + } + for i, cg := range pg.Generators { + if cg.BanHeight == blockchainHeight { // Burn deposit for the generator banned on previous block. + if bErr := g.balances.burnDeposit(cg.AddressID, blockID); bErr != nil { + return fmt.Errorf("failed to burn deposit of previous generator with index %d: %w", + i, bErr) + } + // TODO: Reset deposit here? } } return nil } -func (g *generators) size() int { - return len(g.set) +func (g *generatorsStorage) saveGeneratorsRecord( + record generatorsRecord, height proto.Height, blockID proto.BlockID, +) error { + key := generatorsKey{height: height} + data, err := record.marshalBinary() + if err != nil { + return fmt.Errorf("failed to marshal generators record: %w", err) + } + return g.hs.addNewEntry(generators, key.bytes(), data, blockID) } -func (g *generators) string() string { - sb := strings.Builder{} - sb.WriteRune('[') - for i, gen := range g.set { - if i > 0 { - sb.WriteString(", ") +func (g *generatorsStorage) pushLegacyStateHashRecord( + record stateComponent, height proto.Height, blockID proto.BlockID, +) error { + if g.calculateHashes { + key := generatorsKey{height: height} + if err := g.hasher.push(string(key.bytes()), record, blockID); err != nil { + return fmt.Errorf("failed to hash generators balances record: %w", err) } - sb.WriteString(gen.Address().String()) } - sb.WriteRune(']') - return sb.String() + return nil } -func (g *generators) generator(index uint32) (GeneratorInfo, error) { - if int(index) >= len(g.set) { - return GeneratorInfo{}, fmt.Errorf("generator index %d is out of bounds for generator set of size %d", - index, len(g.set)) +func (g *generatorsStorage) generators(height proto.Height) (*generatorsRecord, error) { + k := generatorsKey{height: height} + data, err := g.hs.newestTopEntryData(k.bytes()) + if err != nil { + if isNotFoundInHistoryOrDBErr(err) { + return nil, ErrNoGeneratorsSet + } + return nil, fmt.Errorf("failed to retrieve generators record for height %d: %w", height, err) + } + r := new(generatorsRecord) + if uErr := r.unmarshalBinary(data); uErr != nil { + return nil, fmt.Errorf("failed to unmarshal generators record for height %d: %w", height, uErr) } - return g.set[index], nil + return r, nil } -func (g *generators) banGenerator(index uint32, blockID proto.BlockID) error { - if int(index) >= len(g.set) { - return fmt.Errorf("generator index %d is out of bounds for the generator set of size %d", - index, len(g.set)) +func (g *generatorsStorage) infos(height proto.Height) ([]GeneratorInfo, error) { + gs, err := g.generators(height) + if err != nil { + return nil, err } - if g.set[index].ban { - return nil // Generator already banned, do nothing. + cs, err := g.getCommitments(height) + if err != nil { + return nil, err } + return buildInfos(cs, gs.Generators, g.settings.AddressSchemeCharacter) +} - // Ban generator for the current block. - g.set[index].ban = true +func (g *generatorsStorage) getCommitments(height proto.Height) ([]commitmentItem, error) { + activationHeight, err := g.fs.newestActivationHeight(int16(settings.DeterministicFinality)) + if err != nil { + if isNotFoundInHistoryOrDBErr(err) { // DeterministicFinality feature is not approved or activated. + return nil, nil // Not an error, commitments are empty. + } + return nil, fmt.Errorf("failed to get activation height for Deterministic Finality feature: %w", err) + } + periodStart, err := CurrentGenerationPeriodStart(activationHeight, height, g.settings.GenerationPeriod) + if err != nil { + return nil, fmt.Errorf("failed to calculate current generation period start: %w", err) + } + return g.commitments.newestCommitments(periodStart) +} - // Save ban for processing of the future blocks. - key := bannedGeneratorsKey{periodStart: g.generationPeriodStart} - keyBytes := key.bytes() - recordBytes, err := g.hs.newestTopEntryData(keyBytes) +func (g *generatorsStorage) string(height proto.Height) (string, error) { + gs, err := g.generators(height) if err != nil { - if isNotFoundInHistoryOrDBErr(err) { // No record found, create new one. - r := bannedGeneratorsRecord{ - Indexes: []uint32{index}, - } - data, mErr := r.marshalBinary() - if mErr != nil { - return fmt.Errorf("failed to marshal record to binary data: %w", mErr) - } - return g.hs.addNewEntry(bannedGenerators, keyBytes, data, blockID) + if errors.Is(err, ErrNoGeneratorsSet) { + return "n/a", nil } - return err + return "", fmt.Errorf("failed to build generators set string for height %d: %w", height, err) } - var r bannedGeneratorsRecord - if uErr := r.unmarshalBinary(recordBytes); uErr != nil { - return fmt.Errorf("failed to unmarshal record from binary data: %w", uErr) + return gs.string(g.settings.AddressSchemeCharacter), nil +} + +func (g *generatorsStorage) generator(index uint32, height proto.Height) (GeneratorInfo, error) { + gs, err := g.generators(height) + if err != nil { + return GeneratorInfo{}, fmt.Errorf("failed to retrieve generator by index %d for height %d: %w", + index, height, err) } - if aErr := r.appendIndex(index); aErr != nil { - return fmt.Errorf("failed to append index to record: %w", aErr) + if s := gs.size(); int(index) >= s { + return GeneratorInfo{}, fmt.Errorf("generator index %d is out of bounds for generator set of size %d", + index, s) } - recordBytes, err = r.marshalBinary() + cms, err := g.getCommitments(height) if err != nil { - return fmt.Errorf("failed to marshal record to binary data: %w", err) + return GeneratorInfo{}, fmt.Errorf("failed to get commitments for height %d: %w", height, err) } - return g.hs.addNewEntry(bannedGenerators, keyBytes, recordBytes, blockID) + return buildGeneratorInfo(index, cms, gs.Generators, g.settings.AddressSchemeCharacter) } -func (g *generators) bans(periodStart uint32) ([]uint32, error) { - key := bannedGeneratorsKey{periodStart: periodStart} - keyBytes := key.bytes() - recordBytes, err := g.hs.newestTopEntryData(keyBytes) +func (g *generatorsStorage) banGenerator(index uint32, height proto.Height, blockID proto.BlockID) error { + gs, err := g.generators(height) if err != nil { - if isNotFoundInHistoryOrDBErr(err) { // No record found, return empty bans list. - return []uint32{}, nil - } - return nil, err + return fmt.Errorf("failed to retrieve generator by index %d for height %d: %w", index, height, err) + } + // Ban generator for the current block. + if bErr := gs.banGenerator(index, height); bErr != nil { + return fmt.Errorf("failed to ban: %w", bErr) } - var r bannedGeneratorsRecord - if uErr := r.unmarshalBinary(recordBytes); uErr != nil { - return nil, fmt.Errorf("failed to unmarshal record from binary data: %w", uErr) + slog.Debug("Generator banned", "index", index, "height", height, + "generators", gs.string(g.settings.AddressSchemeCharacter)) + + // Save ban for processing of the future blocks. + key := generatorsKey{height: height} + data, err := gs.marshalBinary() + if err != nil { + return fmt.Errorf("failed to marshal generators record for height %d: %w", height, err) } - return r.Indexes, nil + return g.hs.addNewEntry(generators, key.bytes(), data, blockID) } // newestGeneratingBalance retrieves the generating balance for a given address and height. This method checks // that given address is in the current generators set. If current generator set is empty, it uses the balance // provider to get the balance for the address. -func (g *generators) newestGeneratingBalance(addr proto.AddressID, height proto.Height) (uint64, error) { - if len(g.set) == 0 { // Generators set is empty just get balance from state. - return g.balances.newestGeneratingBalance(addr, height) - } - for _, gen := range g.set { - if gen.Address().ID() == addr { - if gen.ban { - return 0, fmt.Errorf("address '%s' is banned from generation", gen.Address().String()) +// Note that the height is the height of the state (blockchain height) here. +func (g *generatorsStorage) newestGeneratingBalance( + addr proto.AddressID, blockchainHeight proto.Height, +) (uint64, error) { + blockHeight := blockchainHeight + 1 + gs, err := g.generators(blockHeight) + if err != nil { + if errors.Is(err, ErrNoGeneratorsSet) { + return g.balances.newestGeneratingBalance(addr, blockchainHeight) + } + return 0, fmt.Errorf("failed to retrieve generators for block at height %d: %w", blockHeight, err) + } + if gs.size() == 0 { // Generators set is empty just get balance from state. + return g.balances.newestGeneratingBalance(addr, blockchainHeight) + } + for _, gen := range gs.Generators { + if gen.AddressID == addr { + if gen.BanHeight != 0 { // Generator is banned, produce specific error for clarity. + a, aErr := gen.AddressID.ToWavesAddress(g.settings.AddressSchemeCharacter) + if aErr != nil { + return 0, fmt.Errorf("failed to convert address ID to Waves address: %w", aErr) + } + return 0, fmt.Errorf("address '%s' is banned from generation", a.String()) } - return gen.balance, nil + return gen.Balance, nil } } + // Checked whole generator set, but no generator with given address ID found. a, err := addr.ToWavesAddress(g.settings.AddressSchemeCharacter) if err != nil { return 0, fmt.Errorf("failed to convert address ID to Waves address: %w", err) } - return 0, fmt.Errorf("address '%s' is not in the current generator set %s", a.String(), g.string()) + str, err := g.string(blockHeight) + if err != nil { + return 0, fmt.Errorf("failed to get newest generation balance for block at height %d: %w", + blockHeight, err) + } + return 0, fmt.Errorf("address '%s' is not in the current generator set %s", a.String(), str) } -func (g *generators) findGenerator(lookup func(GeneratorInfo) bool) (GeneratorInfo, error) { - for _, info := range g.set { +func (g *generatorsStorage) findGenerator(height proto.Height, lookup func(GeneratorInfo) bool) (GeneratorInfo, error) { + infos, err := g.infos(height) + if err != nil { + return GeneratorInfo{}, fmt.Errorf("failed to find generator: %w", err) + } + for _, info := range infos { if lookup(info) { return info, nil } @@ -395,42 +642,48 @@ func (g *generators) findGenerator(lookup func(GeneratorInfo) bool) (GeneratorIn // totalGenerationBalance returns generation balance of all commited generators. // If no generators commited (generators set is empty) function returns 0 without an error. // Block generator included in the set. -func (g *generators) totalGenerationBalance() uint64 { - if len(g.set) == 0 { - return 0 +func (g *generatorsStorage) totalGenerationBalance(height proto.Height) (uint64, error) { + gs, err := g.generators(height) + if err != nil { + if errors.Is(err, ErrNoGeneratorsSet) { + return 0, nil + } + return 0, fmt.Errorf("failed to calculate total generation balance for height %d: %w", height, err) + } + if gs.size() == 0 { + return 0, nil } total := uint64(0) - for _, gen := range g.set { - total += gen.GenerationBalance() // Banned generators or generators with insufficient balance return 0 here. + for _, gen := range gs.Generators { + total += gen.Balance // Banned generators or generators with insufficient balance return 0 here. } - return total + return total, nil } -func (g *generators) blockGenerator() (*GeneratorInfo, error) { - if g.blockGeneratorIndex < 0 || g.blockGeneratorIndex >= len(g.set) { - return nil, fmt.Errorf("invalid block generator index %d", g.blockGeneratorIndex) +func (g *generatorsStorage) blockGenerator(height proto.Height) (uint32, *generator, error) { + gs, err := g.generators(height) + if err != nil { + return 0, nil, fmt.Errorf("failed to retrieve generators for height %d: %w", height, err) } - r := g.set[g.blockGeneratorIndex] - return &r, nil -} - -func (g *generators) generatorsByHeight(height proto.Height) ([]GeneratorInfo, error) { - if height != g.blockHeight { - //TODO: Implement. The implementation is possible for extended API only, when the generators are stored for - // every height. - return nil, errors.New("not implemented") + if bgi := int(gs.BlockGeneratorIndex); bgi < 0 || bgi >= gs.size() { + return 0, nil, fmt.Errorf("invalid block generator index %d", bgi) + } + idx, err := safecast.Convert[uint32](gs.BlockGeneratorIndex) + if err != nil { + return 0, nil, fmt.Errorf("failed to convert block generator index: %w", err) } - return slices.Clone(g.set), nil + bg := gs.Generators[idx] + return idx, &bg, nil } -func (g *generators) prepareHashes() error { +func (g *generatorsStorage) prepareHashes() error { if !g.calculateHashes { return nil // No-op if hash calculation is disabled. } return g.hasher.stop() } -func (g *generators) reset() { +func (g *generatorsStorage) reset() { if !g.calculateHashes { return // No-op if hash calculation is disabled. } diff --git a/pkg/state/history_storage.go b/pkg/state/history_storage.go index f93833713b..d1e2d27ec7 100644 --- a/pkg/state/history_storage.go +++ b/pkg/state/history_storage.go @@ -47,7 +47,7 @@ const ( challengedAddress commitment finalization - bannedGenerators + generators ) type blockchainEntityProperties struct { @@ -223,7 +223,7 @@ var properties = map[blockchainEntity]blockchainEntityProperties{ needToCut: true, fixedSize: false, }, - bannedGenerators: { + generators: { needToFilter: true, needToCut: true, fixedSize: false, diff --git a/pkg/state/keys.go b/pkg/state/keys.go index f916a6f6ba..57c2cf3c42 100644 --- a/pkg/state/keys.go +++ b/pkg/state/keys.go @@ -139,8 +139,8 @@ const ( commitmentKeyPrefix // Key to store and retrieve last finalization record. finalizationKeyPrefix - // Key to address the set of generators' indexes that are banned from block generation. - bannedGeneratorsKeyPrefix + // Key to address the set of generators. + generatorsKeyPrefix ) var ( @@ -210,8 +210,8 @@ func prefixByEntity(entity blockchainEntity) ([]byte, error) { return []byte{commitmentKeyPrefix}, nil case finalization: return []byte{finalizationKeyPrefix}, nil - case bannedGenerators: - return []byte{bannedGeneratorsKeyPrefix}, nil + case generators: + return []byte{generatorsKeyPrefix}, nil default: return nil, errors.New("bad entity type") } @@ -798,3 +798,14 @@ func (k *commitmentStateHashKey) string() string { binary.BigEndian.PutUint32(buf[uint32Size:], k.index) return string(buf) } + +type generatorsKey struct { + height uint64 +} + +func (k *generatorsKey) bytes() []byte { + buf := make([]byte, 1+uint64Size) + buf[0] = generatorsKeyPrefix + binary.BigEndian.PutUint64(buf[1:], k.height) + return buf +} diff --git a/pkg/state/mock_state.go b/pkg/state/mock_state.go index 5640c7f2eb..e79e4b0fcd 100644 --- a/pkg/state/mock_state.go +++ b/pkg/state/mock_state.go @@ -1265,9 +1265,75 @@ func (_c *MockState_BlockchainSettings_Call) RunAndReturn(run func() (*settings. return _c } +// BuildLocalEndorsementMessage provides a mock function for the type MockState +func (_mock *MockState) BuildLocalEndorsementMessage(v proto.Height, blockID proto.BlockID) (proto.EndorsementCryptoMessage, error) { + ret := _mock.Called(v, blockID) + + if len(ret) == 0 { + panic("no return value specified for BuildLocalEndorsementMessage") + } + + var r0 proto.EndorsementCryptoMessage + var r1 error + if returnFunc, ok := ret.Get(0).(func(proto.Height, proto.BlockID) (proto.EndorsementCryptoMessage, error)); ok { + return returnFunc(v, blockID) + } + if returnFunc, ok := ret.Get(0).(func(proto.Height, proto.BlockID) proto.EndorsementCryptoMessage); ok { + r0 = returnFunc(v, blockID) + } else { + r0 = ret.Get(0).(proto.EndorsementCryptoMessage) + } + if returnFunc, ok := ret.Get(1).(func(proto.Height, proto.BlockID) error); ok { + r1 = returnFunc(v, blockID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockState_BuildLocalEndorsementMessage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BuildLocalEndorsementMessage' +type MockState_BuildLocalEndorsementMessage_Call struct { + *mock.Call +} + +// BuildLocalEndorsementMessage is a helper method to define mock.On call +// - v proto.Height +// - blockID proto.BlockID +func (_e *MockState_Expecter) BuildLocalEndorsementMessage(v interface{}, blockID interface{}) *MockState_BuildLocalEndorsementMessage_Call { + return &MockState_BuildLocalEndorsementMessage_Call{Call: _e.mock.On("BuildLocalEndorsementMessage", v, blockID)} +} + +func (_c *MockState_BuildLocalEndorsementMessage_Call) Run(run func(v proto.Height, blockID proto.BlockID)) *MockState_BuildLocalEndorsementMessage_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 proto.Height + if args[0] != nil { + arg0 = args[0].(proto.Height) + } + var arg1 proto.BlockID + if args[1] != nil { + arg1 = args[1].(proto.BlockID) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockState_BuildLocalEndorsementMessage_Call) Return(endorsementCryptoMessage proto.EndorsementCryptoMessage, err error) *MockState_BuildLocalEndorsementMessage_Call { + _c.Call.Return(endorsementCryptoMessage, err) + return _c +} + +func (_c *MockState_BuildLocalEndorsementMessage_Call) RunAndReturn(run func(v proto.Height, blockID proto.BlockID) (proto.EndorsementCryptoMessage, error)) *MockState_BuildLocalEndorsementMessage_Call { + _c.Call.Return(run) + return _c +} + // CheckRollbackHeightAuto provides a mock function for the type MockState -func (_mock *MockState) CheckRollbackHeightAuto(height proto.Height) error { - ret := _mock.Called(height) +func (_mock *MockState) CheckRollbackHeightAuto(v proto.Height) error { + ret := _mock.Called(v) if len(ret) == 0 { panic("no return value specified for CheckRollbackHeightAuto") @@ -1275,7 +1341,7 @@ func (_mock *MockState) CheckRollbackHeightAuto(height proto.Height) error { var r0 error if returnFunc, ok := ret.Get(0).(func(proto.Height) error); ok { - r0 = returnFunc(height) + r0 = returnFunc(v) } else { r0 = ret.Error(0) } @@ -1288,12 +1354,12 @@ type MockState_CheckRollbackHeightAuto_Call struct { } // CheckRollbackHeightAuto is a helper method to define mock.On call -// - height proto.Height -func (_e *MockState_Expecter) CheckRollbackHeightAuto(height interface{}) *MockState_CheckRollbackHeightAuto_Call { - return &MockState_CheckRollbackHeightAuto_Call{Call: _e.mock.On("CheckRollbackHeightAuto", height)} +// - v proto.Height +func (_e *MockState_Expecter) CheckRollbackHeightAuto(v interface{}) *MockState_CheckRollbackHeightAuto_Call { + return &MockState_CheckRollbackHeightAuto_Call{Call: _e.mock.On("CheckRollbackHeightAuto", v)} } -func (_c *MockState_CheckRollbackHeightAuto_Call) Run(run func(height proto.Height)) *MockState_CheckRollbackHeightAuto_Call { +func (_c *MockState_CheckRollbackHeightAuto_Call) Run(run func(v proto.Height)) *MockState_CheckRollbackHeightAuto_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 proto.Height if args[0] != nil { @@ -1311,7 +1377,7 @@ func (_c *MockState_CheckRollbackHeightAuto_Call) Return(err error) *MockState_C return _c } -func (_c *MockState_CheckRollbackHeightAuto_Call) RunAndReturn(run func(height proto.Height) error) *MockState_CheckRollbackHeightAuto_Call { +func (_c *MockState_CheckRollbackHeightAuto_Call) RunAndReturn(run func(v proto.Height) error) *MockState_CheckRollbackHeightAuto_Call { _c.Call.Return(run) return _c } @@ -1361,8 +1427,8 @@ func (_c *MockState_Close_Call) RunAndReturn(run func() error) *MockState_Close_ } // CommittedGenerators provides a mock function for the type MockState -func (_mock *MockState) CommittedGenerators(height proto.Height) ([]GeneratorInfo, error) { - ret := _mock.Called(height) +func (_mock *MockState) CommittedGenerators(v proto.Height) ([]GeneratorInfo, error) { + ret := _mock.Called(v) if len(ret) == 0 { panic("no return value specified for CommittedGenerators") @@ -1371,17 +1437,17 @@ func (_mock *MockState) CommittedGenerators(height proto.Height) ([]GeneratorInf var r0 []GeneratorInfo var r1 error if returnFunc, ok := ret.Get(0).(func(proto.Height) ([]GeneratorInfo, error)); ok { - return returnFunc(height) + return returnFunc(v) } if returnFunc, ok := ret.Get(0).(func(proto.Height) []GeneratorInfo); ok { - r0 = returnFunc(height) + r0 = returnFunc(v) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]GeneratorInfo) } } if returnFunc, ok := ret.Get(1).(func(proto.Height) error); ok { - r1 = returnFunc(height) + r1 = returnFunc(v) } else { r1 = ret.Error(1) } @@ -1394,12 +1460,12 @@ type MockState_CommittedGenerators_Call struct { } // CommittedGenerators is a helper method to define mock.On call -// - height proto.Height -func (_e *MockState_Expecter) CommittedGenerators(height interface{}) *MockState_CommittedGenerators_Call { - return &MockState_CommittedGenerators_Call{Call: _e.mock.On("CommittedGenerators", height)} +// - v proto.Height +func (_e *MockState_Expecter) CommittedGenerators(v interface{}) *MockState_CommittedGenerators_Call { + return &MockState_CommittedGenerators_Call{Call: _e.mock.On("CommittedGenerators", v)} } -func (_c *MockState_CommittedGenerators_Call) Run(run func(height proto.Height)) *MockState_CommittedGenerators_Call { +func (_c *MockState_CommittedGenerators_Call) Run(run func(v proto.Height)) *MockState_CommittedGenerators_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 proto.Height if args[0] != nil { @@ -1417,7 +1483,7 @@ func (_c *MockState_CommittedGenerators_Call) Return(generatorInfos []GeneratorI return _c } -func (_c *MockState_CommittedGenerators_Call) RunAndReturn(run func(height proto.Height) ([]GeneratorInfo, error)) *MockState_CommittedGenerators_Call { +func (_c *MockState_CommittedGenerators_Call) RunAndReturn(run func(v proto.Height) ([]GeneratorInfo, error)) *MockState_CommittedGenerators_Call { _c.Call.Return(run) return _c } @@ -1655,8 +1721,8 @@ func (_c *MockState_EstimatorVersion_Call) RunAndReturn(run func() (int, error)) } // FindGenerator provides a mock function for the type MockState -func (_mock *MockState) FindGenerator(fn func(GeneratorInfo) bool) (GeneratorInfo, error) { - ret := _mock.Called(fn) +func (_mock *MockState) FindGenerator(v proto.Height, fn func(GeneratorInfo) bool) (GeneratorInfo, error) { + ret := _mock.Called(v, fn) if len(ret) == 0 { panic("no return value specified for FindGenerator") @@ -1664,16 +1730,16 @@ func (_mock *MockState) FindGenerator(fn func(GeneratorInfo) bool) (GeneratorInf var r0 GeneratorInfo var r1 error - if returnFunc, ok := ret.Get(0).(func(func(GeneratorInfo) bool) (GeneratorInfo, error)); ok { - return returnFunc(fn) + if returnFunc, ok := ret.Get(0).(func(proto.Height, func(GeneratorInfo) bool) (GeneratorInfo, error)); ok { + return returnFunc(v, fn) } - if returnFunc, ok := ret.Get(0).(func(func(GeneratorInfo) bool) GeneratorInfo); ok { - r0 = returnFunc(fn) + if returnFunc, ok := ret.Get(0).(func(proto.Height, func(GeneratorInfo) bool) GeneratorInfo); ok { + r0 = returnFunc(v, fn) } else { r0 = ret.Get(0).(GeneratorInfo) } - if returnFunc, ok := ret.Get(1).(func(func(GeneratorInfo) bool) error); ok { - r1 = returnFunc(fn) + if returnFunc, ok := ret.Get(1).(func(proto.Height, func(GeneratorInfo) bool) error); ok { + r1 = returnFunc(v, fn) } else { r1 = ret.Error(1) } @@ -1686,19 +1752,25 @@ type MockState_FindGenerator_Call struct { } // FindGenerator is a helper method to define mock.On call +// - v proto.Height // - fn func(GeneratorInfo) bool -func (_e *MockState_Expecter) FindGenerator(fn interface{}) *MockState_FindGenerator_Call { - return &MockState_FindGenerator_Call{Call: _e.mock.On("FindGenerator", fn)} +func (_e *MockState_Expecter) FindGenerator(v interface{}, fn interface{}) *MockState_FindGenerator_Call { + return &MockState_FindGenerator_Call{Call: _e.mock.On("FindGenerator", v, fn)} } -func (_c *MockState_FindGenerator_Call) Run(run func(fn func(GeneratorInfo) bool)) *MockState_FindGenerator_Call { +func (_c *MockState_FindGenerator_Call) Run(run func(v proto.Height, fn func(GeneratorInfo) bool)) *MockState_FindGenerator_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 func(GeneratorInfo) bool + var arg0 proto.Height if args[0] != nil { - arg0 = args[0].(func(GeneratorInfo) bool) + arg0 = args[0].(proto.Height) + } + var arg1 func(GeneratorInfo) bool + if args[1] != nil { + arg1 = args[1].(func(GeneratorInfo) bool) } run( arg0, + arg1, ) }) return _c @@ -1709,7 +1781,7 @@ func (_c *MockState_FindGenerator_Call) Return(generatorInfo GeneratorInfo, err return _c } -func (_c *MockState_FindGenerator_Call) RunAndReturn(run func(fn func(GeneratorInfo) bool) (GeneratorInfo, error)) *MockState_FindGenerator_Call { +func (_c *MockState_FindGenerator_Call) RunAndReturn(run func(v proto.Height, fn func(GeneratorInfo) bool) (GeneratorInfo, error)) *MockState_FindGenerator_Call { _c.Call.Return(run) return _c } diff --git a/pkg/state/snapshot_applier.go b/pkg/state/snapshot_applier.go index a7ce526206..09a52b4092 100644 --- a/pkg/state/snapshot_applier.go +++ b/pkg/state/snapshot_applier.go @@ -688,7 +688,6 @@ func (a *blockSnapshotsApplier) ApplyCommitToGeneration(snapshot proto.Generatio } // Here we take the generation period start from the applying transaction, // because the snapshot itself does not contain this information. - // TODO: But maybe it's better to calculate it from the block height? tx, ok := a.txSnapshotContext.applyingTx.(*proto.CommitToGenerationWithProofs) if !ok { return errors.New("failed to apply generation commitment snapshot: applying tx is not CommitToGenerationWithProofs") @@ -715,6 +714,10 @@ func (a *blockSnapshotsApplier) ApplyCommitToGeneration(snapshot proto.Generatio if err = a.stor.balances.setWavesBalance(addr.ID(), value, a.info.BlockID()); err != nil { return errors.Wrapf(err, "failed to get set balance profile for address %q", addr.String()) } + err = tx.GenerateID(a.info.Scheme()) + if err != nil { + return errors.Wrap(err, "failed to get tx ID") + } return a.stor.commitments.store(tx.GenerationPeriodStart, snapshot.SenderPublicKey, snapshot.EndorserPublicKey, - a.info.BlockID()) + *tx.ID, a.info.BlockID()) } diff --git a/pkg/state/state.go b/pkg/state/state.go index 12ce8225da..e2d8adde95 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -73,7 +73,7 @@ type blockchainEntitiesStorage struct { patches *patchesStorage commitments *commitments finality *finality - generators *generators + generators *generatorsStorage settings *settings.BlockchainSettings calculateHashes bool } @@ -1455,12 +1455,12 @@ func (s *stateManager) handleFinalizationUpdate( blockID.String(), blockHeight, err, ) } - if slog.Default().Enabled(context.Background(), slog.LevelInfo) { + if slog.Default().Enabled(context.Background(), slog.LevelDebug) { finalizedHeight, err := s.stor.finality.lastFinalizedHeight(blockHeight) if err != nil { return fmt.Errorf("failed to get last finalization height: %w", err) } - slog.Info("Current finalization state", slog.Uint64("finalizedHeight", finalizedHeight), + slog.Debug("Current finalization state", slog.Uint64("finalizedHeight", finalizedHeight), slog.Uint64("blockHeight", blockHeight), slog.String("blockID", blockID.String())) } return nil @@ -1925,31 +1925,40 @@ func (s *stateManager) resetDeposits(nextBlockID proto.BlockID, lastBlockHeight if err != nil { return fmt.Errorf("failed to reset deposits: %w", err) } - generators, err := s.stor.commitments.newestGenerators(start) + commitedGenerators, err := s.stor.commitments.newestGenerators(start) if err != nil { return fmt.Errorf("failed to reset deposits: %w", err) } - for _, generator := range generators { + if len(commitedGenerators) == 0 { // No commitments on the period, nothing to reset. + slog.Debug("No commited generators for the period, skipping deposit reset", "height", lastBlockHeight) + return nil + } + periodGenerators, err := s.stor.generators.generators(lastBlockHeight) + if err != nil { + return fmt.Errorf("failed to reset deposits: %w", err) + } + if cgl, pgl := len(commitedGenerators), len(periodGenerators.Generators); cgl != pgl { + return fmt.Errorf( + "failed to reset deposits: committed generators count %d is not equal to period generators count %d", + cgl, pgl) + } + for i, generator := range commitedGenerators { addr, adrErr := proto.NewAddressFromPublicKey(s.settings.AddressSchemeCharacter, generator) if adrErr != nil { return fmt.Errorf("failed to reset deposits: %w", adrErr) } - balance, bErr := s.stor.balances.newestWavesBalance(addr.ID()) - if bErr != nil { - return fmt.Errorf("failed to reset deposits: %w", bErr) - } - balance.Deposit, err = common.SubInt(balance.Deposit, Deposit) - if err != nil { - return fmt.Errorf("failed to reset deposits: %w", err) - } - v := wavesValue{ - profile: balance, - leaseChange: false, - balanceChange: false, + pg := periodGenerators.Generators[i] + if pg.BanHeight != 0 { + slog.Debug("Skipping deposit reset for banned generator", "height", lastBlockHeight, + "banHeight", pg.BanHeight, "index", i, "generator", addr.String()) + continue } - if sbErr := s.stor.balances.setWavesBalance(addr.ID(), v, nextBlockID); sbErr != nil { - return fmt.Errorf("failed to reset deposits: %w", sbErr) + before, after, rstErr := s.stor.balances.resetDeposit(addr.ID(), nextBlockID) + if rstErr != nil { + return fmt.Errorf("failed to reset deposits for generator '%s': %w", addr.String(), rstErr) } + slog.Debug("Generator deposit successfully reset", "generator", addr.String(), + "deposit_before", before, "deposit_after", after) } return nil } @@ -3556,13 +3565,13 @@ func (s *stateManager) CalculateVotingFinalization(endorsers []proto.WavesAddres // FindGenerator retrieves the generator's information by a lookup function that is applied to current generators set. // Available lookup functions: ByBLSPublicKey. -func (s *stateManager) FindGenerator(lookup func(GeneratorInfo) bool) (GeneratorInfo, error) { - return s.stor.generators.findGenerator(lookup) +func (s *stateManager) FindGenerator(height proto.Height, lookup func(GeneratorInfo) bool) (GeneratorInfo, error) { + return s.stor.generators.findGenerator(height, lookup) } // CommittedGenerators returns the list of Waves addresses of committed generators. func (s *stateManager) CommittedGenerators(height proto.Height) ([]GeneratorInfo, error) { - return s.stor.generators.generatorsByHeight(height) + return s.stor.generators.infos(height) } func (s *stateManager) LastFinalizedHeight() (proto.Height, error) { @@ -3597,3 +3606,11 @@ func (s *stateManager) LastFinalizedBlock() (*proto.BlockHeader, error) { func (s *stateManager) NewestMinimalGeneratingBalanceAtHeight(height proto.Height, ts uint64) uint64 { return s.stor.features.minimalGeneratingBalanceAtHeight(height, ts) } + +// BuildLocalEndorsementMessage creates endorsement crypto message from local state of finalization at given height and +// for given parent block ID. +func (s *stateManager) BuildLocalEndorsementMessage( + height proto.Height, parentID proto.BlockID, +) (proto.EndorsementCryptoMessage, error) { + return s.stor.finality.buildLocalEndorsementMessage(height, parentID) +} diff --git a/pkg/state/state_hasher_test.go b/pkg/state/state_hasher_test.go index 8fc535e4f7..5bfc237098 100644 --- a/pkg/state/state_hasher_test.go +++ b/pkg/state/state_hasher_test.go @@ -411,7 +411,7 @@ func TestCalculateCommittedGeneratorsBalancesStateHash(t *testing.T) { featureActivationHeight, blockHeight, so.settings.GenerationPeriod, ) require.NoError(t, err) - err = so.entities.commitments.store(periodStart, pk, bls.PublicKey{1, 2, 3, 4, 5}, bID2) + err = so.entities.commitments.store(periodStart, pk, bls.PublicKey{1, 2, 3, 4, 5}, crypto.Digest{6, 7, 8, 9}, bID2) require.NoError(t, err) err = so.entities.generators.initialize(blockchainHeight, bID2, pk, 0) require.NoError(t, err) diff --git a/pkg/state/threadsafe_wrapper.go b/pkg/state/threadsafe_wrapper.go index 17249c113e..5d7b5056e9 100644 --- a/pkg/state/threadsafe_wrapper.go +++ b/pkg/state/threadsafe_wrapper.go @@ -418,10 +418,12 @@ func (a *ThreadSafeReadWrapper) IsActiveLightNodeNewBlocksFields(blockHeight pro return a.s.IsActiveLightNodeNewBlocksFields(blockHeight) } -func (a *ThreadSafeReadWrapper) FindGenerator(lookup func(GeneratorInfo) bool) (GeneratorInfo, error) { +func (a *ThreadSafeReadWrapper) FindGenerator( + height proto.Height, lookup func(GeneratorInfo) bool, +) (GeneratorInfo, error) { a.mu.RLock() defer a.mu.RUnlock() - return a.s.FindGenerator(lookup) + return a.s.FindGenerator(height, lookup) } func (a *ThreadSafeReadWrapper) CommittedGenerators(height proto.Height) ([]GeneratorInfo, error) { @@ -449,6 +451,14 @@ func (a *ThreadSafeReadWrapper) CheckRollbackHeightAuto(height proto.Height) err return a.s.CheckRollbackHeightAuto(height) } +func (a *ThreadSafeReadWrapper) BuildLocalEndorsementMessage( + height proto.Height, parentID proto.BlockID, +) (proto.EndorsementCryptoMessage, error) { + a.mu.RLock() + defer a.mu.RUnlock() + return a.s.BuildLocalEndorsementMessage(height, parentID) +} + func NewThreadSafeReadWrapper(mu *sync.RWMutex, s StateInfo) StateInfo { return &ThreadSafeReadWrapper{ mu: mu, @@ -593,9 +603,9 @@ func (a *ThreadSafeWriteWrapper) unlockUnsafe() { func (a *ThreadSafeWriteWrapper) lock() { if !atomic.CompareAndSwapInt32(a.i, 0, 1) { - // previous value was not `0`, so it means we already locked - // this should never happen, cause all write action happens in only 1 thread. - // most likely than we change state in another thread and it's forbidden + // The previous value was not 0, meaning the state is already being modified. + // This violates the invariant that all writes are single-threaded. + // Reaching this point indicates unexpected concurrent mutation. panic("already modifying state") } a.mu.Lock() diff --git a/pkg/state/transaction_checker_test.go b/pkg/state/transaction_checker_test.go index 42b7ad9373..b30bb3caa5 100644 --- a/pkg/state/transaction_checker_test.go +++ b/pkg/state/transaction_checker_test.go @@ -1907,7 +1907,8 @@ func TestCheckCommitToGenerationWithProofs_SecondCommitmentAttempt(t *testing.T) _, err := to.tc.checkCommitToGenerationWithProofs(tx1, info) assert.NoError(t, err) - err = to.stor.entities.commitments.store(tx1.GenerationPeriodStart, tx1.SenderPK, tx1.EndorserPublicKey, info.blockID) + err = to.stor.entities.commitments.store(tx1.GenerationPeriodStart, tx1.SenderPK, tx1.EndorserPublicKey, *tx1.ID, + info.blockID) require.NoError(t, err) tx2 := createCommitToGenerationWithProofs(t, 10_002,