diff --git a/cmd/neofs-ir/defaults.go b/cmd/neofs-ir/defaults.go index 9f7ef68b..eef6de78 100644 --- a/cmd/neofs-ir/defaults.go +++ b/cmd/neofs-ir/defaults.go @@ -85,4 +85,7 @@ func defaultConfiguration(cfg *viper.Viper) { cfg.SetDefault("netmap_cleaner.threshold", 3) cfg.SetDefault("emit.storage.amount", 0) + cfg.SetDefault("emit.mint.cache_size", 1000) + cfg.SetDefault("emit.mint.threshold", 1) + cfg.SetDefault("emit.mint.value", 20000000) // 0.2 Fixed8 } diff --git a/go.mod b/go.mod index 23103ca9..0b4bb8ca 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/alecthomas/participle v0.6.0 github.com/golang/protobuf v1.4.3 github.com/google/uuid v1.1.1 + github.com/hashicorp/golang-lru v0.5.4 github.com/mitchellh/go-homedir v1.1.0 github.com/mr-tron/base58 v1.1.3 github.com/multiformats/go-multiaddr v0.2.0 diff --git a/pkg/innerring/innerring.go b/pkg/innerring/innerring.go index 40ab0664..2036b6ae 100644 --- a/pkg/innerring/innerring.go +++ b/pkg/innerring/innerring.go @@ -231,15 +231,18 @@ func New(ctx context.Context, log *zap.Logger, cfg *viper.Viper) (*Server, error // create mainnnet neofs processor neofsProcessor, err := neofs.New(&neofs.Params{ - Log: log, - PoolSize: cfg.GetInt("workers.neofs"), - NeoFSContract: server.contracts.neofs, - BalanceContract: server.contracts.balance, - NetmapContract: server.contracts.netmap, - MorphClient: server.morphClient, - EpochState: server, - ActiveState: server, - Converter: &server.precision, + Log: log, + PoolSize: cfg.GetInt("workers.neofs"), + NeoFSContract: server.contracts.neofs, + BalanceContract: server.contracts.balance, + NetmapContract: server.contracts.netmap, + MorphClient: server.morphClient, + EpochState: server, + ActiveState: server, + Converter: &server.precision, + MintEmitCacheSize: cfg.GetInt("emit.mint.cache_size"), + MintEmitThreshold: cfg.GetUint64("emit.mint.threshold"), + MintEmitValue: util.Fixed8(cfg.GetInt64("emit.mint.value")), }) if err != nil { return nil, err diff --git a/pkg/innerring/processors/neofs/process_assets.go b/pkg/innerring/processors/neofs/process_assets.go index 0fe74b01..86664012 100644 --- a/pkg/innerring/processors/neofs/process_assets.go +++ b/pkg/innerring/processors/neofs/process_assets.go @@ -34,12 +34,34 @@ func (np *Processor) processDeposit(deposit *neofsEvent.Deposit) { np.log.Error("can't transfer assets to balance contract", zap.Error(err)) } - // fixme: send gas when ^ tx accepted - // fixme: do not send gas to the same user twice per epoch - err = np.morphClient.TransferGas(deposit.To(), util.Fixed8FromInt64(2)) - if err != nil { - np.log.Error("can't transfer native gas to receiver", zap.Error(err)) + curEpoch := np.epochState.EpochCounter() + receiver := deposit.To() + + // check if receiver already received some mint GAS emissions + // we should lock there even though LRU cache is already thread save + // we lock there because GAS transfer AND cache update must be atomic + np.mintEmitLock.Lock() + defer np.mintEmitLock.Unlock() + + val, ok := np.mintEmitCache.Get(receiver.String()) + if ok && val.(uint64)+np.mintEmitThreshold >= curEpoch { + np.log.Warn("double mint emission declined", + zap.String("receiver", receiver.String()), + zap.Uint64("last_emission", val.(uint64)), + zap.Uint64("current_epoch", curEpoch)) + + return } + + err = np.morphClient.TransferGas(receiver, np.mintEmitValue) + if err != nil { + np.log.Error("can't transfer native gas to receiver", + zap.String("error", err.Error())) + + return + } + + np.mintEmitCache.Add(receiver.String(), curEpoch) } // Process withdraw event by locking assets in balance account. diff --git a/pkg/innerring/processors/neofs/processor.go b/pkg/innerring/processors/neofs/processor.go index 50e64572..3608b469 100644 --- a/pkg/innerring/processors/neofs/processor.go +++ b/pkg/innerring/processors/neofs/processor.go @@ -1,6 +1,9 @@ package neofs import ( + "sync" + + lru "github.com/hashicorp/golang-lru" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neofs-node/pkg/morph/client" "github.com/nspcc-dev/neofs-node/pkg/morph/event" @@ -28,28 +31,35 @@ type ( // Processor of events produced by neofs contract in main net. Processor struct { - log *zap.Logger - pool *ants.Pool - neofsContract util.Uint160 - balanceContract util.Uint160 - netmapContract util.Uint160 - morphClient *client.Client - epochState EpochState - activeState ActiveState - converter PrecisionConverter + log *zap.Logger + pool *ants.Pool + neofsContract util.Uint160 + balanceContract util.Uint160 + netmapContract util.Uint160 + morphClient *client.Client + epochState EpochState + activeState ActiveState + converter PrecisionConverter + mintEmitLock *sync.Mutex + mintEmitCache *lru.Cache + mintEmitThreshold uint64 + mintEmitValue util.Fixed8 } // Params of the processor constructor. Params struct { - Log *zap.Logger - PoolSize int - NeoFSContract util.Uint160 - BalanceContract util.Uint160 - NetmapContract util.Uint160 - MorphClient *client.Client - EpochState EpochState - ActiveState ActiveState - Converter PrecisionConverter + Log *zap.Logger + PoolSize int + NeoFSContract util.Uint160 + BalanceContract util.Uint160 + NetmapContract util.Uint160 + MorphClient *client.Client + EpochState EpochState + ActiveState ActiveState + Converter PrecisionConverter + MintEmitCacheSize int + MintEmitThreshold uint64 // in epochs + MintEmitValue util.Fixed8 } ) @@ -83,16 +93,25 @@ func New(p *Params) (*Processor, error) { return nil, errors.Wrap(err, "ir/neofs: can't create worker pool") } + lruCache, err := lru.New(p.MintEmitCacheSize) + if err != nil { + return nil, errors.Wrap(err, "ir/neofs: can't create LRU cache for gas emission") + } + return &Processor{ - log: p.Log, - pool: pool, - neofsContract: p.NeoFSContract, - balanceContract: p.BalanceContract, - netmapContract: p.NetmapContract, - morphClient: p.MorphClient, - epochState: p.EpochState, - activeState: p.ActiveState, - converter: p.Converter, + log: p.Log, + pool: pool, + neofsContract: p.NeoFSContract, + balanceContract: p.BalanceContract, + netmapContract: p.NetmapContract, + morphClient: p.MorphClient, + epochState: p.EpochState, + activeState: p.ActiveState, + converter: p.Converter, + mintEmitLock: new(sync.Mutex), + mintEmitCache: lruCache, + mintEmitThreshold: p.MintEmitThreshold, + mintEmitValue: p.MintEmitValue, }, nil }