diff --git a/pkg/innerring/alphabet.go b/pkg/innerring/alphabet.go index 78930db7d4..ddb3444038 100644 --- a/pkg/innerring/alphabet.go +++ b/pkg/innerring/alphabet.go @@ -104,13 +104,13 @@ func (l GlagoliticLetter) String() string { return "unknown" } -type alphabetContracts map[GlagoliticLetter]util.Uint160 +type AlphabetContracts map[GlagoliticLetter]util.Uint160 -func newAlphabetContracts() alphabetContracts { +func NewAlphabetContracts() AlphabetContracts { return make(map[GlagoliticLetter]util.Uint160, lastLetterNum) } -func (a alphabetContracts) GetByIndex(ind int) (util.Uint160, bool) { +func (a AlphabetContracts) GetByIndex(ind int) (util.Uint160, bool) { if ind < 0 || ind >= int(lastLetterNum) { return util.Uint160{}, false } @@ -120,16 +120,16 @@ func (a alphabetContracts) GetByIndex(ind int) (util.Uint160, bool) { return contract, ok } -func (a alphabetContracts) indexOutOfRange(ind int) bool { +func (a AlphabetContracts) indexOutOfRange(ind int) bool { return ind < 0 && ind >= len(a) } -func (a alphabetContracts) iterate(f func(GlagoliticLetter, util.Uint160)) { +func (a AlphabetContracts) iterate(f func(GlagoliticLetter, util.Uint160)) { for letter, contract := range a { f(letter, contract) } } -func (a *alphabetContracts) set(l GlagoliticLetter, h util.Uint160) { +func (a *AlphabetContracts) set(l GlagoliticLetter, h util.Uint160) { (*a)[l] = h } diff --git a/pkg/innerring/contracts.go b/pkg/innerring/contracts.go index 32e817787f..55c2ff582a 100644 --- a/pkg/innerring/contracts.go +++ b/pkg/innerring/contracts.go @@ -19,7 +19,7 @@ type contracts struct { processing util.Uint160 // in mainnet frostfsID util.Uint160 // in morph - alphabet alphabetContracts // in morph + alphabet AlphabetContracts // in morph } func parseContracts(cfg *viper.Viper, morph nnsResolver, withoutMainNet, withoutMainNotary, withoutSideNotary bool) (*contracts, error) { @@ -76,9 +76,9 @@ func parseContracts(cfg *viper.Viper, morph nnsResolver, withoutMainNet, without return result, nil } -func parseAlphabetContracts(cfg *viper.Viper, morph nnsResolver) (alphabetContracts, error) { +func parseAlphabetContracts(cfg *viper.Viper, morph nnsResolver) (AlphabetContracts, error) { num := GlagoliticLetter(cfg.GetUint("contracts.alphabet.amount")) - alpha := newAlphabetContracts() + alpha := NewAlphabetContracts() if num > lastLetterNum { return nil, fmt.Errorf("amount of alphabet contracts overflows glagolitsa %d > %d", num, lastLetterNum) diff --git a/pkg/innerring/processors/alphabet/handlers_test.go b/pkg/innerring/processors/alphabet/handlers_test.go new file mode 100644 index 0000000000..5e71cbc908 --- /dev/null +++ b/pkg/innerring/processors/alphabet/handlers_test.go @@ -0,0 +1,282 @@ +package alphabet_test + +import ( + "testing" + "time" + + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring/processors/alphabet" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring/timers" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/stretchr/testify/require" +) + +func TestProcessorEmitsGasToNetmapAndAlphabet(t *testing.T) { + t.Parallel() + var emission uint64 = 100_000 + var index int = 5 + var parsedWallets []util.Uint160 = []util.Uint160{{20}, {25}} + + alphabetContracts := innerring.NewAlphabetContracts() + for i := 0; i <= index; i++ { + alphabetContracts[innerring.GlagoliticLetter(i)] = util.Uint160{uint8(i)} + } + + morphClient := &testMorphClient{} + + var node1 netmap.NodeInfo + key1, err := keys.NewPublicKeyFromString("038c862959e56b43e20f79187c4fe9e0bc7c8c66c1603e6cf0ec7f87ab6b08dc35") + require.NoError(t, err, "failed to parse key1") + node1.SetPublicKey(key1.Bytes()) + + var node2 netmap.NodeInfo + key2, err := keys.NewPublicKeyFromString("02ac920cd7df0b61b289072e6b946e2da4e1a31b9ab1c621bb475e30fa4ab102c3") + require.NoError(t, err, "failed to parse key2") + node2.SetPublicKey(key2.Bytes()) + + nodes := []netmap.NodeInfo{node1, node2} + + network := &netmap.NetMap{} + network.SetNodes(nodes) + + netmapClient := &testNetmapClient{ + netmap: network, + } + + params := &alphabet.Params{ + ParsedWallets: parsedWallets, + Log: test.NewLogger(t, true), + PoolSize: 2, + StorageEmission: emission, + IRList: &testIndexer{index: index}, + AlphabetContracts: alphabetContracts, + MorphClient: morphClient, + NetmapClient: netmapClient, + } + + processor, err := alphabet.New(params) + require.NoError(t, err, "failed to create processor instance") + + processor.HandleGasEmission(timers.NewAlphabetEmitTick{}) + + processor.WaitPoolRunning() + + require.EqualValues(t, []invokedMethod{ + { + contract: alphabetContracts[innerring.GlagoliticLetter(index)], + fee: 0, + method: "emit", + }, + }, morphClient.invokedMethods, "invalid invoked morph methods") + + require.EqualValues(t, []transferGas{ + { + receiver: key1.GetScriptHash(), + amount: fixedn.Fixed8(25_000), + }, + { + receiver: key2.GetScriptHash(), + amount: fixedn.Fixed8(25_000), + }, + }, morphClient.transferedGas, "invalid transfered Gas") + + require.EqualValues(t, []batchTransferGas{ + { + receivers: parsedWallets, + amount: fixedn.Fixed8(25_000), + }, + }, morphClient.batchTransferedGas, "invalid batch transfered Gas") +} + +func TestProcessorEmitsGasToNetmapIfNoParsedWallets(t *testing.T) { + t.Parallel() + var emission uint64 = 100_000 + var index int = 5 + var parsedWallets []util.Uint160 = []util.Uint160{} + + alphabetContracts := innerring.NewAlphabetContracts() + for i := 0; i <= index; i++ { + alphabetContracts[innerring.GlagoliticLetter(i)] = util.Uint160{uint8(i)} + } + + morphClient := &testMorphClient{} + + var node1 netmap.NodeInfo + key1, err := keys.NewPublicKeyFromString("038c862959e56b43e20f79187c4fe9e0bc7c8c66c1603e6cf0ec7f87ab6b08dc35") + require.NoError(t, err, "failed to parse key1") + node1.SetPublicKey(key1.Bytes()) + + var node2 netmap.NodeInfo + key2, err := keys.NewPublicKeyFromString("02ac920cd7df0b61b289072e6b946e2da4e1a31b9ab1c621bb475e30fa4ab102c3") + require.NoError(t, err, "failed to parse key2") + node2.SetPublicKey(key2.Bytes()) + + nodes := []netmap.NodeInfo{node1, node2} + + network := &netmap.NetMap{} + network.SetNodes(nodes) + + netmapClient := &testNetmapClient{ + netmap: network, + } + + params := &alphabet.Params{ + ParsedWallets: parsedWallets, + Log: test.NewLogger(t, true), + PoolSize: 2, + StorageEmission: emission, + IRList: &testIndexer{index: index}, + AlphabetContracts: alphabetContracts, + MorphClient: morphClient, + NetmapClient: netmapClient, + } + + processor, err := alphabet.New(params) + require.NoError(t, err, "failed to create processor instance") + + processor.HandleGasEmission(timers.NewAlphabetEmitTick{}) + + time.Sleep(time.Second) + + require.EqualValues(t, []invokedMethod{ + { + contract: alphabetContracts[innerring.GlagoliticLetter(index)], + fee: 0, + method: "emit", + }, + }, morphClient.invokedMethods, "invalid invoked morph methods") + + require.EqualValues(t, []transferGas{ + { + receiver: key1.GetScriptHash(), + amount: fixedn.Fixed8(50_000), + }, + { + receiver: key2.GetScriptHash(), + amount: fixedn.Fixed8(50_000), + }, + }, morphClient.transferedGas, "invalid transfered Gas") + + require.Equal(t, 0, len(morphClient.batchTransferedGas), "invalid batch transfered Gas") +} + +func TestProcessorDoesntEmitGasIfNoNetmapOrParsedWallets(t *testing.T) { + t.Parallel() + var emission uint64 = 100_000 + var index int = 5 + var parsedWallets []util.Uint160 = []util.Uint160{} + + alphabetContracts := innerring.NewAlphabetContracts() + for i := 0; i <= index; i++ { + alphabetContracts[innerring.GlagoliticLetter(i)] = util.Uint160{uint8(i)} + } + + morphClient := &testMorphClient{} + + nodes := []netmap.NodeInfo{} + network := &netmap.NetMap{} + network.SetNodes(nodes) + + netmapClient := &testNetmapClient{ + netmap: network, + } + + params := &alphabet.Params{ + ParsedWallets: parsedWallets, + Log: test.NewLogger(t, true), + PoolSize: 2, + StorageEmission: emission, + IRList: &testIndexer{index: index}, + AlphabetContracts: alphabetContracts, + MorphClient: morphClient, + NetmapClient: netmapClient, + } + + processor, err := alphabet.New(params) + require.NoError(t, err, "failed to create processor instance") + + processor.HandleGasEmission(timers.NewAlphabetEmitTick{}) + + time.Sleep(time.Second) + + require.EqualValues(t, []invokedMethod{ + { + contract: alphabetContracts[innerring.GlagoliticLetter(index)], + fee: 0, + method: "emit", + }, + }, morphClient.invokedMethods, "invalid invoked morph methods") + + require.Equal(t, 0, len(morphClient.transferedGas), "invalid transfered Gas") + + require.Equal(t, 0, len(morphClient.batchTransferedGas), "invalid batch transfered Gas") +} + +type testIndexer struct { + index int +} + +func (i *testIndexer) AlphabetIndex() int { + return i.index +} + +type invokedMethod struct { + contract util.Uint160 + fee fixedn.Fixed8 + method string + args []any +} + +type transferGas struct { + receiver util.Uint160 + amount fixedn.Fixed8 +} + +type batchTransferGas struct { + receivers []util.Uint160 + amount fixedn.Fixed8 +} + +type testMorphClient struct { + invokedMethods []invokedMethod + transferedGas []transferGas + batchTransferedGas []batchTransferGas +} + +func (c *testMorphClient) Invoke(contract util.Uint160, fee fixedn.Fixed8, method string, args ...any) error { + c.invokedMethods = append(c.invokedMethods, + invokedMethod{ + contract: contract, + fee: fee, + method: method, + args: args, + }) + return nil +} +func (c *testMorphClient) TransferGas(receiver util.Uint160, amount fixedn.Fixed8) error { + c.transferedGas = append(c.transferedGas, transferGas{ + receiver: receiver, + amount: amount, + }) + return nil +} + +func (c *testMorphClient) BatchTransferGas(receivers []util.Uint160, amount fixedn.Fixed8) error { + c.batchTransferedGas = append(c.batchTransferedGas, batchTransferGas{ + receivers: receivers, + amount: amount, + }) + return nil +} + +type testNetmapClient struct { + netmap *netmap.NetMap +} + +func (c *testNetmapClient) NetMap() (*netmap.NetMap, error) { + return c.netmap, nil +} diff --git a/pkg/innerring/processors/alphabet/processor.go b/pkg/innerring/processors/alphabet/processor.go index 169bfb3e20..c2d7c11649 100644 --- a/pkg/innerring/processors/alphabet/processor.go +++ b/pkg/innerring/processors/alphabet/processor.go @@ -3,12 +3,13 @@ package alphabet import ( "errors" "fmt" + "time" "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" - "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" - nmClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" + "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/panjf2000/ants/v2" "go.uber.org/zap" @@ -32,14 +33,24 @@ type ( GetByIndex(int) (util.Uint160, bool) } + netmapClient interface { + NetMap() (*netmap.NetMap, error) + } + + morphClient interface { + Invoke(contract util.Uint160, fee fixedn.Fixed8, method string, args ...any) error + TransferGas(receiver util.Uint160, amount fixedn.Fixed8) error + BatchTransferGas(receivers []util.Uint160, amount fixedn.Fixed8) error + } + // Processor of events produced for alphabet contracts in the sidechain. Processor struct { parsedWallets []util.Uint160 log *logger.Logger pool *ants.Pool alphabetContracts Contracts - netmapClient *nmClient.Client - morphClient *client.Client + netmapClient netmapClient + morphClient morphClient irList Indexer storageEmission uint64 } @@ -50,8 +61,8 @@ type ( Log *logger.Logger PoolSize int AlphabetContracts Contracts - NetmapClient *nmClient.Client - MorphClient *client.Client + NetmapClient netmapClient + MorphClient morphClient IRList Indexer StorageEmission uint64 } @@ -106,3 +117,11 @@ func (ap *Processor) ListenerNotaryParsers() []event.NotaryParserInfo { func (ap *Processor) ListenerNotaryHandlers() []event.NotaryHandlerInfo { return nil } + +// WaitPoolRunning waits while pool has running tasks +// For use in test only. +func (ap *Processor) WaitPoolRunning() { + for ap.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } +}