diff --git a/pkg/innerring/alphabet.go b/pkg/innerring/alphabet.go index 78930db7d..ddb344403 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/blocktimer.go b/pkg/innerring/blocktimer.go index 94e262099..922fe51ff 100644 --- a/pkg/innerring/blocktimer.go +++ b/pkg/innerring/blocktimer.go @@ -33,6 +33,10 @@ type ( newEpochHandler func() + containerEstimationStopper interface { + StopEstimation(p container.StopEstimationPrm) error + } + epochTimerArgs struct { l *logger.Logger @@ -40,8 +44,8 @@ type ( newEpochHandlers []newEpochHandler - cnrWrapper *container.Client // to invoke stop container estimation - epoch epochState // to specify which epoch to stop, and epoch duration + cnrWrapper containerEstimationStopper // to invoke stop container estimation + epoch epochState // to specify which epoch to stop, and epoch duration stopEstimationDMul uint32 // X: X/Y of epoch in blocks stopEstimationDDiv uint32 // Y: X/Y of epoch in blocks diff --git a/pkg/innerring/blocktimer_test.go b/pkg/innerring/blocktimer_test.go new file mode 100644 index 000000000..e1a79c2a8 --- /dev/null +++ b/pkg/innerring/blocktimer_test.go @@ -0,0 +1,167 @@ +package innerring + +import ( + "testing" + + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test" + "github.com/stretchr/testify/require" +) + +func TestEpochTimer(t *testing.T) { + t.Parallel() + alphaState := &testAlphabetState{isAlphabet: true} + neh := &testNewEpochHandler{} + cnrStopper := &testContainerEstStopper{} + epochState := &testEpochState{ + counter: 99, + duration: 10, + } + collectHandler := &testEventHandler{} + distributeHandler := &testEventHandler{} + + args := &epochTimerArgs{ + l: test.NewLogger(t, true), + alphabetState: alphaState, + newEpochHandlers: []newEpochHandler{neh.Handle}, + cnrWrapper: cnrStopper, + epoch: epochState, + stopEstimationDMul: 2, + stopEstimationDDiv: 10, + collectBasicIncome: subEpochEventHandler{ + handler: collectHandler.Handle, + durationMul: 3, + durationDiv: 10, + }, + distributeBasicIncome: subEpochEventHandler{ + handler: distributeHandler.Handle, + durationMul: 4, + durationDiv: 10, + }, + } + et := newEpochTimer(args) + err := et.Reset() + require.NoError(t, err, "failed to reset timer") + + et.Tick(100) + require.Equal(t, 0, neh.called, "invalid new epoch handler calls") + require.Equal(t, 0, cnrStopper.called, "invalid container stop handler calls") + require.Equal(t, 0, collectHandler.called, "invalid collect basic income calls") + require.Equal(t, 0, distributeHandler.called, "invalid distribute basic income calls") + + et.Tick(101) + require.Equal(t, 0, neh.called, "invalid new epoch handler calls") + require.Equal(t, 1, cnrStopper.called, "invalid container stop handler calls") + require.Equal(t, 0, collectHandler.called, "invalid collect basic income calls") + require.Equal(t, 0, distributeHandler.called, "invalid distribute basic income calls") + + et.Tick(102) + require.Equal(t, 0, neh.called, "invalid new epoch handler calls") + require.Equal(t, 1, cnrStopper.called, "invalid container stop handler calls") + require.Equal(t, 1, collectHandler.called, "invalid collect basic income calls") + require.Equal(t, 0, distributeHandler.called, "invalid distribute basic income calls") + + et.Tick(103) + require.Equal(t, 0, neh.called, "invalid new epoch handler calls") + require.Equal(t, 1, cnrStopper.called, "invalid container stop handler calls") + require.Equal(t, 1, collectHandler.called, "invalid collect basic income calls") + require.Equal(t, 1, distributeHandler.called, "invalid distribute basic income calls") + + var h uint32 + for h = 104; h < 109; h++ { + et.Tick(h) + require.Equal(t, 0, neh.called, "invalid new epoch handler calls") + require.Equal(t, 1, cnrStopper.called, "invalid container stop handler calls") + require.Equal(t, 1, collectHandler.called, "invalid collect basic income calls") + require.Equal(t, 1, distributeHandler.called, "invalid distribute basic income calls") + } + + et.Tick(109) + require.Equal(t, 1, neh.called, "invalid new epoch handler calls") + require.Equal(t, 1, cnrStopper.called, "invalid container stop handler calls") + require.Equal(t, 1, collectHandler.called, "invalid collect basic income calls") + require.Equal(t, 1, distributeHandler.called, "invalid distribute basic income calls") + + et.Tick(110) + require.Equal(t, 1, neh.called, "invalid new epoch handler calls") + require.Equal(t, 1, cnrStopper.called, "invalid container stop handler calls") + require.Equal(t, 1, collectHandler.called, "invalid collect basic income calls") + require.Equal(t, 1, distributeHandler.called, "invalid distribute basic income calls") + + et.Tick(111) + require.Equal(t, 1, neh.called, "invalid new epoch handler calls") + require.Equal(t, 2, cnrStopper.called, "invalid container stop handler calls") + require.Equal(t, 1, collectHandler.called, "invalid collect basic income calls") + require.Equal(t, 1, distributeHandler.called, "invalid distribute basic income calls") + + et.Tick(112) + require.Equal(t, 1, neh.called, "invalid new epoch handler calls") + require.Equal(t, 2, cnrStopper.called, "invalid container stop handler calls") + require.Equal(t, 2, collectHandler.called, "invalid collect basic income calls") + require.Equal(t, 1, distributeHandler.called, "invalid distribute basic income calls") + + et.Tick(113) + require.Equal(t, 1, neh.called, "invalid new epoch handler calls") + require.Equal(t, 2, cnrStopper.called, "invalid container stop handler calls") + require.Equal(t, 2, collectHandler.called, "invalid collect basic income calls") + require.Equal(t, 2, distributeHandler.called, "invalid distribute basic income calls") + + for h = 114; h < 119; h++ { + et.Tick(h) + require.Equal(t, 1, neh.called, "invalid new epoch handler calls") + require.Equal(t, 2, cnrStopper.called, "invalid container stop handler calls") + require.Equal(t, 2, collectHandler.called, "invalid collect basic income calls") + require.Equal(t, 2, distributeHandler.called, "invalid distribute basic income calls") + } + et.Tick(120) + require.Equal(t, 2, neh.called, "invalid new epoch handler calls") + require.Equal(t, 2, cnrStopper.called, "invalid container stop handler calls") + require.Equal(t, 2, collectHandler.called, "invalid collect basic income calls") + require.Equal(t, 2, distributeHandler.called, "invalid distribute basic income calls") +} + +type testAlphabetState struct { + isAlphabet bool +} + +func (s *testAlphabetState) IsAlphabet() bool { + return s.isAlphabet +} + +type testNewEpochHandler struct { + called int +} + +func (h *testNewEpochHandler) Handle() { + h.called++ +} + +type testContainerEstStopper struct { + called int +} + +func (s *testContainerEstStopper) StopEstimation(_ container.StopEstimationPrm) error { + s.called++ + return nil +} + +type testEpochState struct { + counter uint64 + duration uint64 +} + +func (s *testEpochState) EpochCounter() uint64 { + return s.counter +} +func (s *testEpochState) EpochDuration() uint64 { + return s.duration +} + +type testEventHandler struct { + called int +} + +func (h *testEventHandler) Handle(e event.Event) { + h.called++ +} diff --git a/pkg/innerring/config/fee_test.go b/pkg/innerring/config/fee_test.go new file mode 100644 index 000000000..a0c56aac1 --- /dev/null +++ b/pkg/innerring/config/fee_test.go @@ -0,0 +1,68 @@ +package config + +import ( + "strings" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" + "github.com/spf13/viper" + "github.com/stretchr/testify/require" +) + +func TestConfig(t *testing.T) { + t.Parallel() + t.Run("all set", func(t *testing.T) { + t.Parallel() + file := strings.NewReader( + ` +fee: + main_chain: 50000000 + side_chain: 200000000 + named_container_register: 2500000000 +`, + ) + v := viper.New() + v.SetConfigType("yaml") + err := v.ReadConfig(file) + require.NoError(t, err, "read config file failed") + + config := NewFeeConfig(v) + require.Equal(t, fixedn.Fixed8(50000000), config.MainChainFee(), "main chain fee invalid") + require.Equal(t, fixedn.Fixed8(200000000), config.SideChainFee(), "side chain fee invalid") + require.Equal(t, fixedn.Fixed8(2500000000), config.NamedContainerRegistrationFee(), "named container register fee invalid") + }) + + t.Run("nothing set", func(t *testing.T) { + t.Parallel() + file := strings.NewReader("") + v := viper.New() + v.SetConfigType("yaml") + err := v.ReadConfig(file) + require.NoError(t, err, "read config file failed") + + config := NewFeeConfig(v) + require.Equal(t, fixedn.Fixed8(0), config.MainChainFee(), "main chain fee invalid") + require.Equal(t, fixedn.Fixed8(0), config.SideChainFee(), "side chain fee invalid") + require.Equal(t, fixedn.Fixed8(0), config.NamedContainerRegistrationFee(), "named container register fee invalid") + }) + + t.Run("partially set", func(t *testing.T) { + t.Parallel() + file := strings.NewReader( + ` +fee: + main_chain: 10 +`, + ) + v := viper.New() + v.SetConfigType("yaml") + err := v.ReadConfig(file) + require.NoError(t, err, "read config file failed") + + config := NewFeeConfig(v) + require.Equal(t, fixedn.Fixed8(10), config.MainChainFee(), "main chain fee invalid") + require.Equal(t, fixedn.Fixed8(0), config.SideChainFee(), "side chain fee invalid") + require.Equal(t, fixedn.Fixed8(0), config.NamedContainerRegistrationFee(), "named container register fee invalid") + }) + +} diff --git a/pkg/innerring/contracts.go b/pkg/innerring/contracts.go index def55f22a..d990c7d97 100644 --- a/pkg/innerring/contracts.go +++ b/pkg/innerring/contracts.go @@ -20,10 +20,10 @@ 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 *client.Client, withoutMainNet, withoutMainNotary, withoutSideNotary bool) (*contracts, error) { +func parseContracts(cfg *viper.Viper, morph nnsResolver, withoutMainNet, withoutMainNotary, withoutSideNotary bool) (*contracts, error) { var ( result = new(contracts) err error @@ -78,9 +78,9 @@ func parseContracts(cfg *viper.Viper, morph *client.Client, withoutMainNet, with return result, nil } -func parseAlphabetContracts(cfg *viper.Viper, morph *client.Client) (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) @@ -117,7 +117,7 @@ func parseAlphabetContracts(cfg *viper.Viper, morph *client.Client) (alphabetCon return alpha, nil } -func parseContract(cfg *viper.Viper, morph *client.Client, cfgName, nnsName string) (res util.Uint160, err error) { +func parseContract(cfg *viper.Viper, morph nnsResolver, cfgName, nnsName string) (res util.Uint160, err error) { contractStr := cfg.GetString(cfgName) if len(contractStr) == 0 { return morph.NNSContractAddress(nnsName) @@ -125,3 +125,7 @@ func parseContract(cfg *viper.Viper, morph *client.Client, cfgName, nnsName stri return util.Uint160DecodeStringLE(contractStr) } + +type nnsResolver interface { + NNSContractAddress(name string) (sh util.Uint160, err error) +} diff --git a/pkg/innerring/contracts_test.go b/pkg/innerring/contracts_test.go new file mode 100644 index 000000000..2d6e69c05 --- /dev/null +++ b/pkg/innerring/contracts_test.go @@ -0,0 +1,222 @@ +package innerring + +import ( + "strings" + "testing" + + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/spf13/viper" + "github.com/stretchr/testify/require" +) + +func TestParseContractsSuccess(t *testing.T) { + t.Parallel() + file := strings.NewReader(` +contracts: + frostfs: ee3dee6d05dc79c24a5b8f6985e10d68b7cacc62 + processing: 597f5894867113a41e192801709c02497f611de8 + audit: 219e37aed2180b87e7fe945dbf97d67125e8d73f + balance: d2aa48d14b17b11bc4c68205027884a96706dd16 + container: ed4a7a66fe3f9bfe50f214b49be8f215a3c886b6 + frostfsid: 9f5866decbc751a099e74c7c7bc89f609201755a + netmap: 83c600c81d47a1b1b7cf58eb49ae7ee7240dc742 + proxy: abc8794bb40a21f2db5f21ae62741eb46c8cad1c + alphabet: + amount: 2 + az: c1d211fceeb4b1dc76b8e4054d11fdf887e418ea + buky: e2ba789320899658b100f331bdebb74474757920 +`) + + v := viper.New() + v.SetConfigType("yaml") + err := v.ReadConfig(file) + require.NoError(t, err, "read config file failed") + + t.Run("all enabled", func(t *testing.T) { + t.Parallel() + c, err := parseContracts(v, nil, false, false, false) + require.NoError(t, err, "failed to parse contracts") + + frostfsExp, _ := util.Uint160DecodeStringLE("ee3dee6d05dc79c24a5b8f6985e10d68b7cacc62") + require.Equal(t, frostfsExp, c.frostfs, "invalid frostfs") + + processingExp, _ := util.Uint160DecodeStringLE("597f5894867113a41e192801709c02497f611de8") + require.Equal(t, processingExp, c.processing, "invalid processing") + + auditExp, _ := util.Uint160DecodeStringLE("219e37aed2180b87e7fe945dbf97d67125e8d73f") + require.Equal(t, auditExp, c.audit, "invalid audit") + + balanceExp, _ := util.Uint160DecodeStringLE("d2aa48d14b17b11bc4c68205027884a96706dd16") + require.Equal(t, balanceExp, c.balance, "invalid balance") + + containerExp, _ := util.Uint160DecodeStringLE("ed4a7a66fe3f9bfe50f214b49be8f215a3c886b6") + require.Equal(t, containerExp, c.container, "invalid container") + + frostfsIDExp, _ := util.Uint160DecodeStringLE("9f5866decbc751a099e74c7c7bc89f609201755a") + require.Equal(t, frostfsIDExp, c.frostfsID, "invalid frostfsID") + + netmapIDExp, _ := util.Uint160DecodeStringLE("83c600c81d47a1b1b7cf58eb49ae7ee7240dc742") + require.Equal(t, netmapIDExp, c.netmap, "invalid netmap") + + proxyExp, _ := util.Uint160DecodeStringLE("abc8794bb40a21f2db5f21ae62741eb46c8cad1c") + require.Equal(t, proxyExp, c.proxy, "invalid proxy") + + require.Equal(t, 2, len(c.alphabet), "invalid alphabet contracts length") + + azExp, _ := util.Uint160DecodeStringLE("c1d211fceeb4b1dc76b8e4054d11fdf887e418ea") + require.Equal(t, azExp, c.alphabet[az], "invalid az") + + bukyExp, _ := util.Uint160DecodeStringLE("e2ba789320899658b100f331bdebb74474757920") + require.Equal(t, bukyExp, c.alphabet[buky], "invalid buky") + }) + + t.Run("all disabled", func(t *testing.T) { + t.Parallel() + c, err := parseContracts(v, nil, true, true, true) + require.NoError(t, err, "failed to parse contracts") + + require.Equal(t, util.Uint160{}, c.frostfs, "invalid frostfs") + + require.Equal(t, util.Uint160{}, c.processing, "invalid processing") + + auditExp, _ := util.Uint160DecodeStringLE("219e37aed2180b87e7fe945dbf97d67125e8d73f") + require.Equal(t, auditExp, c.audit, "invalid audit") + + balanceExp, _ := util.Uint160DecodeStringLE("d2aa48d14b17b11bc4c68205027884a96706dd16") + require.Equal(t, balanceExp, c.balance, "invalid balance") + + containerExp, _ := util.Uint160DecodeStringLE("ed4a7a66fe3f9bfe50f214b49be8f215a3c886b6") + require.Equal(t, containerExp, c.container, "invalid container") + + frostfsIDExp, _ := util.Uint160DecodeStringLE("9f5866decbc751a099e74c7c7bc89f609201755a") + require.Equal(t, frostfsIDExp, c.frostfsID, "invalid frostfsID") + + netmapIDExp, _ := util.Uint160DecodeStringLE("83c600c81d47a1b1b7cf58eb49ae7ee7240dc742") + require.Equal(t, netmapIDExp, c.netmap, "invalid netmap") + + require.Equal(t, util.Uint160{}, c.proxy, "invalid proxy") + + require.Equal(t, 2, len(c.alphabet), "invalid alphabet contracts length") + + azExp, _ := util.Uint160DecodeStringLE("c1d211fceeb4b1dc76b8e4054d11fdf887e418ea") + require.Equal(t, azExp, c.alphabet[az], "invalid az") + + bukyExp, _ := util.Uint160DecodeStringLE("e2ba789320899658b100f331bdebb74474757920") + require.Equal(t, bukyExp, c.alphabet[buky], "invalid buky") + }) + + t.Run("main notary & side notary disabled", func(t *testing.T) { + t.Parallel() + c, err := parseContracts(v, nil, false, true, true) + require.NoError(t, err, "failed to parse contracts") + + frostfsExp, _ := util.Uint160DecodeStringLE("ee3dee6d05dc79c24a5b8f6985e10d68b7cacc62") + require.Equal(t, frostfsExp, c.frostfs, "invalid frostfs") + + require.Equal(t, util.Uint160{}, c.processing, "invalid processing") + + auditExp, _ := util.Uint160DecodeStringLE("219e37aed2180b87e7fe945dbf97d67125e8d73f") + require.Equal(t, auditExp, c.audit, "invalid audit") + + balanceExp, _ := util.Uint160DecodeStringLE("d2aa48d14b17b11bc4c68205027884a96706dd16") + require.Equal(t, balanceExp, c.balance, "invalid balance") + + containerExp, _ := util.Uint160DecodeStringLE("ed4a7a66fe3f9bfe50f214b49be8f215a3c886b6") + require.Equal(t, containerExp, c.container, "invalid container") + + frostfsIDExp, _ := util.Uint160DecodeStringLE("9f5866decbc751a099e74c7c7bc89f609201755a") + require.Equal(t, frostfsIDExp, c.frostfsID, "invalid frostfsID") + + netmapIDExp, _ := util.Uint160DecodeStringLE("83c600c81d47a1b1b7cf58eb49ae7ee7240dc742") + require.Equal(t, netmapIDExp, c.netmap, "invalid netmap") + + require.Equal(t, util.Uint160{}, c.proxy, "invalid proxy") + + require.Equal(t, 2, len(c.alphabet), "invalid alphabet contracts length") + + azExp, _ := util.Uint160DecodeStringLE("c1d211fceeb4b1dc76b8e4054d11fdf887e418ea") + require.Equal(t, azExp, c.alphabet[az], "invalid az") + + bukyExp, _ := util.Uint160DecodeStringLE("e2ba789320899658b100f331bdebb74474757920") + require.Equal(t, bukyExp, c.alphabet[buky], "invalid buky") + }) +} + +func TestParseContractsInvalid(t *testing.T) { + t.Parallel() + t.Run("invalid audit contract", func(t *testing.T) { + t.Parallel() + file := strings.NewReader(` +contracts: + frostfs: invalid_data + processing: 597f5894867113a41e192801709c02497f611de8 + audit: 219e37aed2180b87e7fe945dbf97d67125e8d73f + balance: d2aa48d14b17b11bc4c68205027884a96706dd16 + container: ed4a7a66fe3f9bfe50f214b49be8f215a3c886b6 + frostfsid: 9f5866decbc751a099e74c7c7bc89f609201755a + netmap: 83c600c81d47a1b1b7cf58eb49ae7ee7240dc742 + proxy: abc8794bb40a21f2db5f21ae62741eb46c8cad1c + alphabet: + amount: 2 + az: c1d211fceeb4b1dc76b8e4054d11fdf887e418ea + buky: e2ba789320899658b100f331bdebb74474757920 +`) + + v := viper.New() + v.SetConfigType("yaml") + err := v.ReadConfig(file) + require.NoError(t, err, "read config file failed") + + _, err = parseContracts(v, nil, false, false, false) + require.Error(t, err, "unexpected success") + }) + + t.Run("invalid alphabet count", func(t *testing.T) { + t.Parallel() + file := strings.NewReader(` +contracts: + frostfs: ee3dee6d05dc79c24a5b8f6985e10d68b7cacc62 + processing: 597f5894867113a41e192801709c02497f611de8 + audit: 219e37aed2180b87e7fe945dbf97d67125e8d73f + balance: d2aa48d14b17b11bc4c68205027884a96706dd16 + container: ed4a7a66fe3f9bfe50f214b49be8f215a3c886b6 + frostfsid: 9f5866decbc751a099e74c7c7bc89f609201755a + netmap: 83c600c81d47a1b1b7cf58eb49ae7ee7240dc742 + proxy: abc8794bb40a21f2db5f21ae62741eb46c8cad1c + alphabet: + amount: 3 + az: c1d211fceeb4b1dc76b8e4054d11fdf887e418ea + buky: e2ba789320899658b100f331bdebb74474757920 +`) + + v := viper.New() + v.SetConfigType("yaml") + err := v.ReadConfig(file) + require.NoError(t, err, "read config file failed") + + azExp, _ := util.Uint160DecodeStringLE("c1d211fceeb4b1dc76b8e4054d11fdf887e418ea") + bukyExp, _ := util.Uint160DecodeStringLE("e2ba789320899658b100f331bdebb74474757920") + + morph := &testParserMorph{ + values: map[string]util.Uint160{ + "az": azExp, + "buky": bukyExp, + }, + } + + _, err = parseContracts(v, morph, false, false, false) + require.ErrorContains(t, err, "could not read all contracts: required 3, read 2", "unexpected success") + }) +} + +type testParserMorph struct { + values map[string]util.Uint160 +} + +func (m *testParserMorph) NNSContractAddress(name string) (sh util.Uint160, err error) { + if value, found := m.values[name]; found { + return value, nil + } + return util.Uint160{}, client.ErrNNSRecordNotFound +} diff --git a/pkg/innerring/indexer_test.go b/pkg/innerring/indexer_test.go new file mode 100644 index 000000000..493ae92de --- /dev/null +++ b/pkg/innerring/indexer_test.go @@ -0,0 +1,225 @@ +package innerring + +import ( + "fmt" + "testing" + "time" + + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/stretchr/testify/require" + "go.uber.org/atomic" +) + +func TestIndexerReturnsIndexes(t *testing.T) { + t.Parallel() + commiteeKeys, err := keys.NewPublicKeysFromStrings([]string{ + "03ff65b6ae79134a4dce9d0d39d3851e9bab4ee97abf86e81e1c5bbc50cd2826ae", + "022bb4041c50d607ff871dec7e4cd7778388e0ea6849d84ccbd9aa8f32e16a8131", + }) + require.NoError(t, err, "convert string to commitee public keys failed") + cf := &testCommiteeFetcher{ + keys: commiteeKeys, + } + + irKeys, err := keys.NewPublicKeysFromStrings([]string{ + "038c862959e56b43e20f79187c4fe9e0bc7c8c66c1603e6cf0ec7f87ab6b08dc35", + "02ac920cd7df0b61b289072e6b946e2da4e1a31b9ab1c621bb475e30fa4ab102c3", + "022bb4041c50d607ff871dec7e4cd7778388e0ea6849d84ccbd9aa8f32e16a8131", + }) + require.NoError(t, err, "convert string to IR public keys failed") + irf := &testIRFetcher{ + keys: irKeys, + } + + t.Run("success", func(t *testing.T) { + t.Parallel() + key := irKeys[2] + + indexer := newInnerRingIndexer(cf, irf, key, time.Second) + + idx, err := indexer.AlphabetIndex() + require.NoError(t, err, "failed to get alphabet index") + require.Equal(t, int32(1), idx, "invalid alphabet index") + + idx, err = indexer.InnerRingIndex() + require.NoError(t, err, "failed to get IR index") + require.Equal(t, int32(2), idx, "invalid IR index") + + size, err := indexer.InnerRingSize() + require.NoError(t, err, "failed to get IR size") + require.Equal(t, int32(3), size, "invalid IR size") + }) + + t.Run("not found alphabet", func(t *testing.T) { + t.Parallel() + key := irKeys[0] + + indexer := newInnerRingIndexer(cf, irf, key, time.Second) + + idx, err := indexer.AlphabetIndex() + require.NoError(t, err, "failed to get alphabet index") + require.Equal(t, int32(-1), idx, "invalid alphabet index") + + idx, err = indexer.InnerRingIndex() + require.NoError(t, err, "failed to get IR index") + require.Equal(t, int32(0), idx, "invalid IR index") + }) + + t.Run("not found IR", func(t *testing.T) { + t.Parallel() + key := commiteeKeys[0] + + indexer := newInnerRingIndexer(cf, irf, key, time.Second) + + idx, err := indexer.AlphabetIndex() + require.NoError(t, err, "failed to get alphabet index") + require.Equal(t, int32(0), idx, "invalid alphabet index") + + idx, err = indexer.InnerRingIndex() + require.NoError(t, err, "failed to get IR index") + require.Equal(t, int32(-1), idx, "invalid IR index") + }) +} + +func TestIndexerCachesIndexes(t *testing.T) { + t.Parallel() + commiteeKeys, err := keys.NewPublicKeysFromStrings([]string{}) + require.NoError(t, err, "convert string to commitee public keys failed") + cf := &testCommiteeFetcher{ + keys: commiteeKeys, + } + + irKeys, err := keys.NewPublicKeysFromStrings([]string{}) + require.NoError(t, err, "convert string to IR public keys failed") + irf := &testIRFetcher{ + keys: irKeys, + } + + key, err := keys.NewPublicKeyFromString("022bb4041c50d607ff871dec7e4cd7778388e0ea6849d84ccbd9aa8f32e16a8131") + require.NoError(t, err, "convert string to public key failed") + + indexer := newInnerRingIndexer(cf, irf, key, time.Second) + + idx, err := indexer.AlphabetIndex() + require.NoError(t, err, "failed to get alphabet index") + require.Equal(t, int32(-1), idx, "invalid alphabet index") + + idx, err = indexer.InnerRingIndex() + require.NoError(t, err, "failed to get IR index") + require.Equal(t, int32(-1), idx, "invalid IR index") + + size, err := indexer.InnerRingSize() + require.NoError(t, err, "failed to get IR size") + require.Equal(t, int32(0), size, "invalid IR size") + + require.Equal(t, int32(1), cf.calls.Load(), "invalid commitee calls count") + require.Equal(t, int32(1), irf.calls.Load(), "invalid IR calls count") + + idx, err = indexer.AlphabetIndex() + require.NoError(t, err, "failed to get alphabet index") + require.Equal(t, int32(-1), idx, "invalid alphabet index") + + idx, err = indexer.InnerRingIndex() + require.NoError(t, err, "failed to get IR index") + require.Equal(t, int32(-1), idx, "invalid IR index") + + size, err = indexer.InnerRingSize() + require.NoError(t, err, "failed to get IR size") + require.Equal(t, int32(0), size, "invalid IR size") + + require.Equal(t, int32(1), cf.calls.Load(), "invalid commitee calls count") + require.Equal(t, int32(1), irf.calls.Load(), "invalid IR calls count") + + time.Sleep(2 * time.Second) + + idx, err = indexer.AlphabetIndex() + require.NoError(t, err, "failed to get alphabet index") + require.Equal(t, int32(-1), idx, "invalid alphabet index") + + idx, err = indexer.InnerRingIndex() + require.NoError(t, err, "failed to get IR index") + require.Equal(t, int32(-1), idx, "invalid IR index") + + size, err = indexer.InnerRingSize() + require.NoError(t, err, "failed to get IR size") + require.Equal(t, int32(0), size, "invalid IR size") + + require.Equal(t, int32(2), cf.calls.Load(), "invalid commitee calls count") + require.Equal(t, int32(2), irf.calls.Load(), "invalid IR calls count") +} + +func TestIndexerThrowsErrors(t *testing.T) { + t.Parallel() + cf := &testCommiteeFetcher{ + err: fmt.Errorf("test commitee error"), + } + + irKeys, err := keys.NewPublicKeysFromStrings([]string{}) + require.NoError(t, err, "convert string to IR public keys failed") + irf := &testIRFetcher{ + keys: irKeys, + } + + key, err := keys.NewPublicKeyFromString("022bb4041c50d607ff871dec7e4cd7778388e0ea6849d84ccbd9aa8f32e16a8131") + require.NoError(t, err, "convert string to public key failed") + + indexer := newInnerRingIndexer(cf, irf, key, time.Second) + + idx, err := indexer.AlphabetIndex() + require.ErrorContains(t, err, "test commitee error", "error from commitee not throwed") + require.Equal(t, int32(0), idx, "invalid alphabet index") + + idx, err = indexer.InnerRingIndex() + require.ErrorContains(t, err, "test commitee error", "error from IR not throwed") + require.Equal(t, int32(0), idx, "invalid IR index") + + size, err := indexer.InnerRingSize() + require.ErrorContains(t, err, "test commitee error", "error from IR not throwed") + require.Equal(t, int32(0), size, "invalid IR size") + + commiteeKeys, err := keys.NewPublicKeysFromStrings([]string{}) + require.NoError(t, err, "convert string to commitee public keys failed") + cf = &testCommiteeFetcher{ + keys: commiteeKeys, + } + + irf = &testIRFetcher{ + err: fmt.Errorf("test IR error"), + } + + indexer = newInnerRingIndexer(cf, irf, key, time.Second) + + idx, err = indexer.AlphabetIndex() + require.ErrorContains(t, err, "test IR error", "error from commitee not throwed") + require.Equal(t, int32(0), idx, "invalid alphabet index") + + idx, err = indexer.InnerRingIndex() + require.ErrorContains(t, err, "test IR error", "error from IR not throwed") + require.Equal(t, int32(0), idx, "invalid IR index") + + size, err = indexer.InnerRingSize() + require.ErrorContains(t, err, "test IR error", "error from IR not throwed") + require.Equal(t, int32(0), size, "invalid IR size") +} + +type testCommiteeFetcher struct { + keys keys.PublicKeys + err error + calls atomic.Int32 +} + +func (f *testCommiteeFetcher) Committee() (keys.PublicKeys, error) { + f.calls.Inc() + return f.keys, f.err +} + +type testIRFetcher struct { + keys keys.PublicKeys + err error + calls atomic.Int32 +} + +func (f *testIRFetcher) InnerRingKeys() (keys.PublicKeys, error) { + f.calls.Inc() + return f.keys, f.err +} diff --git a/pkg/innerring/initialization.go b/pkg/innerring/initialization.go index 38bde1412..bd043e604 100644 --- a/pkg/innerring/initialization.go +++ b/pkg/innerring/initialization.go @@ -63,7 +63,7 @@ func (s *Server) initNetmapProcessor(cfg *viper.Viper, s.netmapProcessor, err = netmap.New(&netmap.Params{ Log: s.log, PoolSize: cfg.GetInt("workers.netmap"), - NetmapClient: s.netmapClient, + NetmapClient: netmap.NewNetmapClient(s.netmapClient), EpochTimer: s, EpochState: s, AlphabetState: s, diff --git a/pkg/innerring/processors/alphabet/handlers_test.go b/pkg/innerring/processors/alphabet/handlers_test.go new file mode 100644 index 000000000..5e71cbc90 --- /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 169bfb3e2..c2d7c1164 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) + } +} diff --git a/pkg/innerring/processors/balance/handlers_test.go b/pkg/innerring/processors/balance/handlers_test.go new file mode 100644 index 000000000..3470fba2d --- /dev/null +++ b/pkg/innerring/processors/balance/handlers_test.go @@ -0,0 +1,90 @@ +package balance + +import ( + "testing" + "time" + + frostfscontract "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfs" + balanceEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/balance" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/stretchr/testify/require" +) + +func TestProcessorCallsFrostFSContractForLockEvent(t *testing.T) { + t.Parallel() + as := &testAlphabetState{ + isAlphabet: true, + } + conv := &testPresicionConverter{} + cl := &testFrostFSContractClient{} + bsc := util.Uint160{100} + + processor, err := New(&Params{ + Log: test.NewLogger(t, true), + PoolSize: 2, + FrostFSClient: cl, + BalanceSC: bsc, + AlphabetState: as, + Converter: conv, + }) + require.NoError(t, err, "failed to create processor") + + processor.handleLock(balanceEvent.Lock{}) + + for processor.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + require.Equal(t, 1, cl.chequeCalls, "invalid Cheque calls") +} + +func TestProcessorDoesntCallFrostFSContractIfNotAlphabet(t *testing.T) { + t.Parallel() + as := &testAlphabetState{} + conv := &testPresicionConverter{} + cl := &testFrostFSContractClient{} + bsc := util.Uint160{100} + + processor, err := New(&Params{ + Log: test.NewLogger(t, true), + PoolSize: 2, + FrostFSClient: cl, + BalanceSC: bsc, + AlphabetState: as, + Converter: conv, + }) + require.NoError(t, err, "failed to create processor") + + processor.handleLock(balanceEvent.Lock{}) + + for processor.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + require.Equal(t, 0, cl.chequeCalls, "invalid Cheque calls") +} + +type testAlphabetState struct { + isAlphabet bool +} + +func (s *testAlphabetState) IsAlphabet() bool { + return s.isAlphabet +} + +type testPresicionConverter struct { +} + +func (c *testPresicionConverter) ToFixed8(v int64) int64 { + return v +} + +type testFrostFSContractClient struct { + chequeCalls int +} + +func (c *testFrostFSContractClient) Cheque(p frostfscontract.ChequePrm) error { + c.chequeCalls++ + return nil +} diff --git a/pkg/innerring/processors/balance/processor.go b/pkg/innerring/processors/balance/processor.go index 7ae639e89..356754cfb 100644 --- a/pkg/innerring/processors/balance/processor.go +++ b/pkg/innerring/processors/balance/processor.go @@ -25,11 +25,15 @@ type ( ToFixed8(int64) int64 } + FrostFSClient interface { + Cheque(p frostfscontract.ChequePrm) error + } + // Processor of events produced by balance contract in the morphchain. Processor struct { log *logger.Logger pool *ants.Pool - frostfsClient *frostfscontract.Client + frostfsClient FrostFSClient balanceSC util.Uint160 alphabetState AlphabetState converter PrecisionConverter @@ -39,7 +43,7 @@ type ( Params struct { Log *logger.Logger PoolSize int - FrostFSClient *frostfscontract.Client + FrostFSClient FrostFSClient BalanceSC util.Uint160 AlphabetState AlphabetState Converter PrecisionConverter diff --git a/pkg/innerring/processors/container/handlers.go b/pkg/innerring/processors/container/handlers.go index 8d260808b..2ab1147c8 100644 --- a/pkg/innerring/processors/container/handlers.go +++ b/pkg/innerring/processors/container/handlers.go @@ -36,7 +36,7 @@ func (cp *Processor) handleDelete(ev event.Event) { // send an event to the worker pool - err := cp.pool.Submit(func() { cp.processContainerDelete(&del) }) + err := cp.pool.Submit(func() { cp.processContainerDelete(del) }) if err != nil { // there system can be moved into controlled degradation stage cp.log.Warn(logs.ContainerContainerProcessorWorkerPoolDrained, diff --git a/pkg/innerring/processors/container/handlers_test.go b/pkg/innerring/processors/container/handlers_test.go new file mode 100644 index 000000000..6075ff577 --- /dev/null +++ b/pkg/innerring/processors/container/handlers_test.go @@ -0,0 +1,337 @@ +package container + +import ( + "crypto/ecdsa" + "encoding/hex" + "testing" + "time" + + containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" + cntClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfsid" + containerEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/container" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test" + apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" + containerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto" + frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/network/payload" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/stretchr/testify/require" +) + +func TestPutEvent(t *testing.T) { + t.Parallel() + nst := &testNetworkState{ + homHashDisabled: true, + epoch: 100, + } + cc := &testContainerClient{ + get: make(map[string]*containercore.Container), + } + + proc, err := New(&Params{ + Log: test.NewLogger(t, true), + PoolSize: 2, + AlphabetState: &testAlphabetState{isAlphabet: true}, + FrostFSIDClient: &testIDClient{}, + NotaryDisabled: true, + NetworkState: nst, + ContainerClient: cc, + }) + require.NoError(t, err, "failed to create processor") + + p, err := keys.NewPrivateKey() + require.NoError(t, err) + var usr user.ID + user.IDFromKey(&usr, (ecdsa.PublicKey)(*p.PublicKey())) + + var pp netmap.PlacementPolicy + pp.AddReplicas(netmap.ReplicaDescriptor{}) + + var cnr containerSDK.Container + cnr.Init() + cnr.SetOwner(usr) + cnr.SetPlacementPolicy(pp) + cnr.SetBasicACL(acl.Private) + containerSDK.DisableHomomorphicHashing(&cnr) + + event := &testPutEvent{ + cnr: &cnr, + pk: p, + st: nil, + } + + proc.handlePut(event) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + var expectedPut cntClient.PutPrm + expectedPut.SetContainer(cnr.Marshal()) + expectedPut.SetKey(p.PublicKey().Bytes()) + expectedPut.SetSignature(p.Sign(cnr.Marshal())) + expectedPut.SetZone("container") + + require.EqualValues(t, []cntClient.PutPrm{expectedPut}, cc.put, "invalid put requests") +} + +func TestDeleteEvent(t *testing.T) { + t.Parallel() + nst := &testNetworkState{ + homHashDisabled: true, + epoch: 100, + } + cc := &testContainerClient{ + get: make(map[string]*containercore.Container), + } + + p, err := keys.NewPrivateKey() + require.NoError(t, err) + + idc := &testIDClient{ + publicKeys: []*keys.PublicKey{ + p.PublicKey(), + }, + } + + proc, err := New(&Params{ + Log: test.NewLogger(t, true), + PoolSize: 2, + AlphabetState: &testAlphabetState{isAlphabet: true}, + FrostFSIDClient: idc, + NotaryDisabled: true, + NetworkState: nst, + ContainerClient: cc, + }) + require.NoError(t, err, "failed to create processor") + + var usr user.ID + user.IDFromKey(&usr, (ecdsa.PublicKey)(*p.PublicKey())) + + var pp netmap.PlacementPolicy + pp.AddReplicas(netmap.ReplicaDescriptor{}) + + var cnr containerSDK.Container + cnr.Init() + cnr.SetOwner(usr) + cnr.SetPlacementPolicy(pp) + cnr.SetBasicACL(acl.Private) + containerSDK.DisableHomomorphicHashing(&cnr) + + var cid cid.ID + containerSDK.CalculateID(&cid, cnr) + cidBin := make([]byte, 32) + cid.Encode(cidBin) + + ev := containerEvent.Delete{ + ContainerIDValue: cidBin, + SignatureValue: p.Sign(cidBin), + } + + var signature frostfscrypto.Signature + signer := frostfsecdsa.Signer(p.PrivateKey) + require.NoError(t, signature.Calculate(signer, ev.ContainerID()), "failed to calculate signature") + cc.get[hex.EncodeToString(ev.ContainerID())] = &containercore.Container{ + Value: cnr, + Signature: signature, + } + + proc.handleDelete(ev) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + var expectedDelete cntClient.DeletePrm + expectedDelete.SetCID(ev.ContainerID()) + expectedDelete.SetSignature(ev.Signature()) + + require.EqualValues(t, []cntClient.DeletePrm{expectedDelete}, cc.delete, "invalid delete requests") +} + +func TestSetEACLEvent(t *testing.T) { + t.Parallel() + nst := &testNetworkState{ + homHashDisabled: true, + epoch: 100, + } + cc := &testContainerClient{ + get: make(map[string]*containercore.Container), + } + + proc, err := New(&Params{ + Log: test.NewLogger(t, true), + PoolSize: 2, + AlphabetState: &testAlphabetState{isAlphabet: true}, + FrostFSIDClient: &testIDClient{}, + NotaryDisabled: true, + NetworkState: nst, + ContainerClient: cc, + }) + require.NoError(t, err, "failed to create processor") + + p, err := keys.NewPrivateKey() + require.NoError(t, err) + + var usr user.ID + user.IDFromKey(&usr, (ecdsa.PublicKey)(*p.PublicKey())) + + var pp netmap.PlacementPolicy + pp.AddReplicas(netmap.ReplicaDescriptor{}) + + var cnr containerSDK.Container + cnr.Init() + cnr.SetOwner(usr) + cnr.SetPlacementPolicy(pp) + cnr.SetBasicACL(acl.PrivateExtended) + containerSDK.DisableHomomorphicHashing(&cnr) + + var cid cid.ID + containerSDK.CalculateID(&cid, cnr) + cidBytes := make([]byte, 32) + cid.Encode(cidBytes) + + var signature frostfscrypto.Signature + signer := frostfsecdsa.Signer(p.PrivateKey) + require.NoError(t, signature.Calculate(signer, cidBytes), "failed to calculate signature") + + cc.get[hex.EncodeToString(cidBytes)] = &containercore.Container{ + Value: cnr, + Signature: signature, + } + + table := eacl.NewTable() + table.SetCID(cid) + table.SetVersion(version.Current()) + + r := &eacl.Record{} + r.AddObjectContainerIDFilter(eacl.MatchStringEqual, cid) + + table.AddRecord(r) + + event := containerEvent.SetEACL{ + TableValue: table.ToV2().StableMarshal(nil), + PublicKeyValue: p.PublicKey().Bytes(), + SignatureValue: p.Sign(table.ToV2().StableMarshal(nil)), + } + + proc.handleSetEACL(event) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + var expectedPutEACL cntClient.PutEACLPrm + expectedPutEACL.SetTable(table.ToV2().StableMarshal(nil)) + expectedPutEACL.SetKey(p.PublicKey().Bytes()) + expectedPutEACL.SetSignature(p.Sign(table.ToV2().StableMarshal(nil))) + + require.EqualValues(t, []cntClient.PutEACLPrm{expectedPutEACL}, cc.putEACL, "invalid set EACL requests") +} + +type testAlphabetState struct { + isAlphabet bool +} + +func (s *testAlphabetState) IsAlphabet() bool { + return s.isAlphabet +} + +type testNetworkState struct { + homHashDisabled bool + epoch uint64 +} + +func (s *testNetworkState) HomomorphicHashDisabled() (bool, error) { + return s.homHashDisabled, nil +} + +func (s *testNetworkState) Epoch() (uint64, error) { + return s.epoch, nil +} + +type testContainerClient struct { + contractAddress util.Uint160 + put []cntClient.PutPrm + get map[string]*containercore.Container + delete []cntClient.DeletePrm + putEACL []cntClient.PutEACLPrm +} + +func (c *testContainerClient) ContractAddress() util.Uint160 { + return c.contractAddress +} + +func (c *testContainerClient) Morph() *client.Client { + return nil +} + +func (c *testContainerClient) Put(p cntClient.PutPrm) error { + c.put = append(c.put, p) + return nil +} + +func (c *testContainerClient) Get(cid []byte) (*containercore.Container, error) { + key := hex.EncodeToString(cid) + if cont, found := c.get[key]; found { + return cont, nil + } + return nil, apistatus.ContainerNotFound{} +} + +func (c *testContainerClient) Delete(p cntClient.DeletePrm) error { + c.delete = append(c.delete, p) + return nil +} + +func (c *testContainerClient) PutEACL(p cntClient.PutEACLPrm) error { + c.putEACL = append(c.putEACL, p) + return nil +} + +type testIDClient struct { + publicKeys keys.PublicKeys +} + +func (c *testIDClient) AccountKeys(p frostfsid.AccountKeysPrm) (keys.PublicKeys, error) { + return c.publicKeys, nil +} + +var _ putEvent = &testPutEvent{} + +type testPutEvent struct { + cnr *containerSDK.Container + pk *keys.PrivateKey + st []byte +} + +func (e *testPutEvent) MorphEvent() {} + +func (e *testPutEvent) Container() []byte { + return e.cnr.Marshal() +} + +func (e *testPutEvent) PublicKey() []byte { + return e.pk.PublicKey().Bytes() +} + +func (e *testPutEvent) Signature() []byte { + return e.pk.Sign(e.cnr.Marshal()) +} + +func (e *testPutEvent) SessionToken() []byte { + return e.st +} +func (e *testPutEvent) NotaryRequest() *payload.P2PNotaryRequest { + return nil +} diff --git a/pkg/innerring/processors/container/process_container.go b/pkg/innerring/processors/container/process_container.go index c4421c453..603ab86d1 100644 --- a/pkg/innerring/processors/container/process_container.go +++ b/pkg/innerring/processors/container/process_container.go @@ -120,7 +120,7 @@ func (cp *Processor) approvePutContainer(ctx *putContainerContext) { // Process delete container operation from the user by checking container sanity // and sending approve tx back to morph. -func (cp *Processor) processContainerDelete(e *containerEvent.Delete) { +func (cp *Processor) processContainerDelete(e containerEvent.Delete) { if !cp.alphabetState.IsAlphabet() { cp.log.Info(logs.ContainerNonAlphabetModeIgnoreContainerDelete) return @@ -138,7 +138,7 @@ func (cp *Processor) processContainerDelete(e *containerEvent.Delete) { cp.approveDeleteContainer(e) } -func (cp *Processor) checkDeleteContainer(e *containerEvent.Delete) error { +func (cp *Processor) checkDeleteContainer(e containerEvent.Delete) error { binCnr := e.ContainerID() var idCnr cid.ID @@ -170,7 +170,7 @@ func (cp *Processor) checkDeleteContainer(e *containerEvent.Delete) error { return nil } -func (cp *Processor) approveDeleteContainer(e *containerEvent.Delete) { +func (cp *Processor) approveDeleteContainer(e containerEvent.Delete) { var err error prm := cntClient.DeletePrm{} diff --git a/pkg/innerring/processors/container/process_eacl.go b/pkg/innerring/processors/container/process_eacl.go index fce75c678..a8d880c8f 100644 --- a/pkg/innerring/processors/container/process_eacl.go +++ b/pkg/innerring/processors/container/process_eacl.go @@ -6,13 +6,13 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" cntClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container" - "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/container" + containerEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/container" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" "go.uber.org/zap" ) -func (cp *Processor) processSetEACL(e container.SetEACL) { +func (cp *Processor) processSetEACL(e containerEvent.SetEACL) { if !cp.alphabetState.IsAlphabet() { cp.log.Info(logs.ContainerNonAlphabetModeIgnoreSetEACL) return @@ -30,7 +30,7 @@ func (cp *Processor) processSetEACL(e container.SetEACL) { cp.approveSetEACL(e) } -func (cp *Processor) checkSetEACL(e container.SetEACL) error { +func (cp *Processor) checkSetEACL(e containerEvent.SetEACL) error { binTable := e.Table() // unmarshal table @@ -74,7 +74,7 @@ func (cp *Processor) checkSetEACL(e container.SetEACL) error { return nil } -func (cp *Processor) approveSetEACL(e container.SetEACL) { +func (cp *Processor) approveSetEACL(e containerEvent.SetEACL) { var err error prm := cntClient.PutEACLPrm{} diff --git a/pkg/innerring/processors/container/processor.go b/pkg/innerring/processors/container/processor.go index 321596eb4..d5af5e394 100644 --- a/pkg/innerring/processors/container/processor.go +++ b/pkg/innerring/processors/container/processor.go @@ -5,12 +5,16 @@ import ( "fmt" "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" - "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container" + containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" + cntClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfsid" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event" containerEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/container" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger" "github.com/nspcc-dev/neo-go/pkg/core/mempoolevent" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/util" "github.com/panjf2000/ants/v2" "go.uber.org/zap" ) @@ -21,13 +25,26 @@ type ( IsAlphabet() bool } + ContClient interface { + ContractAddress() util.Uint160 + Morph() *client.Client + Put(p cntClient.PutPrm) error + Get(cid []byte) (*containercore.Container, error) + Delete(p cntClient.DeletePrm) error + PutEACL(p cntClient.PutEACLPrm) error + } + + IDClient interface { + AccountKeys(p frostfsid.AccountKeysPrm) (keys.PublicKeys, error) + } + // Processor of events produced by container contract in the sidechain. Processor struct { log *logger.Logger pool *ants.Pool alphabetState AlphabetState - cnrClient *container.Client // notary must be enabled - idClient *frostfsid.Client + cnrClient ContClient // notary must be enabled + idClient IDClient netState NetworkState notaryDisabled bool } @@ -37,8 +54,8 @@ type ( Log *logger.Logger PoolSize int AlphabetState AlphabetState - ContainerClient *container.Client - FrostFSIDClient *frostfsid.Client + ContainerClient ContClient + FrostFSIDClient IDClient NetworkState NetworkState NotaryDisabled bool } diff --git a/pkg/innerring/processors/frostfs/handlers.go b/pkg/innerring/processors/frostfs/handlers.go index 4822cac2c..574cf057a 100644 --- a/pkg/innerring/processors/frostfs/handlers.go +++ b/pkg/innerring/processors/frostfs/handlers.go @@ -18,7 +18,7 @@ func (np *Processor) handleDeposit(ev event.Event) { // send event to the worker pool - err := np.pool.Submit(func() { np.processDeposit(&deposit) }) + err := np.pool.Submit(func() { np.processDeposit(deposit) }) if err != nil { // there system can be moved into controlled degradation stage np.log.Warn(logs.FrostFSFrostfsProcessorWorkerPoolDrained, @@ -34,7 +34,7 @@ func (np *Processor) handleWithdraw(ev event.Event) { // send event to the worker pool - err := np.pool.Submit(func() { np.processWithdraw(&withdraw) }) + err := np.pool.Submit(func() { np.processWithdraw(withdraw) }) if err != nil { // there system can be moved into controlled degradation stage np.log.Warn(logs.FrostFSFrostfsProcessorWorkerPoolDrained, @@ -50,7 +50,7 @@ func (np *Processor) handleCheque(ev event.Event) { // send event to the worker pool - err := np.pool.Submit(func() { np.processCheque(&cheque) }) + err := np.pool.Submit(func() { np.processCheque(cheque) }) if err != nil { // there system can be moved into controlled degradation stage np.log.Warn(logs.FrostFSFrostfsProcessorWorkerPoolDrained, @@ -67,7 +67,7 @@ func (np *Processor) handleConfig(ev event.Event) { // send event to the worker pool - err := np.pool.Submit(func() { np.processConfig(&cfg) }) + err := np.pool.Submit(func() { np.processConfig(cfg) }) if err != nil { // there system can be moved into controlled degradation stage np.log.Warn(logs.FrostFSFrostfsProcessorWorkerPoolDrained, @@ -83,7 +83,7 @@ func (np *Processor) handleBind(ev event.Event) { // send event to the worker pool - err := np.pool.Submit(func() { np.processBind(e) }) + err := np.pool.Submit(func() { np.processBind(e, true) }) if err != nil { // there system can be moved into controlled degradation stage np.log.Warn(logs.FrostFSFrostfsProcessorWorkerPoolDrained, @@ -99,7 +99,7 @@ func (np *Processor) handleUnbind(ev event.Event) { // send event to the worker pool - err := np.pool.Submit(func() { np.processBind(e) }) + err := np.pool.Submit(func() { np.processBind(e, false) }) if err != nil { // there system can be moved into controlled degradation stage np.log.Warn(logs.FrostFSFrostfsProcessorWorkerPoolDrained, diff --git a/pkg/innerring/processors/frostfs/handlers_test.go b/pkg/innerring/processors/frostfs/handlers_test.go new file mode 100644 index 000000000..ee2a00427 --- /dev/null +++ b/pkg/innerring/processors/frostfs/handlers_test.go @@ -0,0 +1,371 @@ +package frostfs + +import ( + "testing" + "time" + + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/balance" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfsid" + nmClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap" + frostfsEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/frostfs" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" + "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 TestHandleDeposit(t *testing.T) { + t.Parallel() + es := &testEpochState{ + epochCounter: 100, + } + b := &testBalaceClient{} + m := &testMorphClient{ + balance: 150, + } + proc, err := newTestProc(t, func(p *Params) { + p.EpochState = es + p.BalanceClient = b + p.MorphClient = m + }) + require.NoError(t, err, "failed to create processor") + + ev := frostfsEvent.Deposit{ + IDValue: []byte{1, 2, 3, 4, 5}, + FromValue: util.Uint160{100}, + ToValue: util.Uint160{200}, + AmountValue: 1000, + } + + proc.handleDeposit(ev) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + var expMint balance.MintPrm + expMint.SetAmount(ev.AmountValue) + expMint.SetID(ev.IDValue) + expMint.SetTo(ev.ToValue) + + require.EqualValues(t, []balance.MintPrm{expMint}, b.mint, "invalid mint value") + require.EqualValues(t, []transferGas{ + { + receiver: ev.ToValue, + amount: fixedn.Fixed8(50), + }, + }, m.transferGas, "invalid transfer gas") + + es.epochCounter = 109 + + proc.handleDeposit(ev) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + expMint.SetAmount(ev.AmountValue) + expMint.SetID(ev.IDValue) + expMint.SetTo(ev.ToValue) + + require.EqualValues(t, []balance.MintPrm{expMint, expMint}, b.mint, "invalid mint value") + require.EqualValues(t, []transferGas{ + { + receiver: ev.ToValue, + amount: fixedn.Fixed8(50), + }, + }, m.transferGas, "invalid transfer gas") +} + +func TestHandleWithdraw(t *testing.T) { + t.Parallel() + es := &testEpochState{ + epochCounter: 100, + } + b := &testBalaceClient{} + m := &testMorphClient{ + balance: 150, + } + proc, err := newTestProc(t, func(p *Params) { + p.EpochState = es + p.BalanceClient = b + p.MorphClient = m + }) + require.NoError(t, err, "failed to create processor") + + ev := frostfsEvent.Withdraw{ + IDValue: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, + UserValue: util.Uint160{100}, + AmountValue: 1000, + } + + proc.handleWithdraw(ev) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + lock, err := util.Uint160DecodeBytesBE(ev.ID()[:util.Uint160Size]) + require.NoError(t, err, "failed to decode ID") + var expLock balance.LockPrm + expLock.SetAmount(ev.AmountValue) + expLock.SetID(ev.IDValue) + expLock.SetDueEpoch(int64(es.epochCounter) + int64(lockAccountLifetime)) + expLock.SetLock(lock) + expLock.SetUser(ev.UserValue) + + require.EqualValues(t, []balance.LockPrm{expLock}, b.lock, "invalid lock value") +} + +func TestHandleCheque(t *testing.T) { + t.Parallel() + es := &testEpochState{ + epochCounter: 100, + } + b := &testBalaceClient{} + m := &testMorphClient{ + balance: 150, + } + proc, err := newTestProc(t, func(p *Params) { + p.BalanceClient = b + p.MorphClient = m + p.EpochState = es + }) + require.NoError(t, err, "failed to create processor") + + ev := frostfsEvent.Cheque{ + IDValue: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, + UserValue: util.Uint160{100}, + AmountValue: 1000, + LockValue: util.Uint160{200}, + } + + proc.handleCheque(ev) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + var expBurn balance.BurnPrm + expBurn.SetAmount(ev.AmountValue) + expBurn.SetID(ev.IDValue) + expBurn.SetTo(util.Uint160{200}) + + require.EqualValues(t, []balance.BurnPrm{expBurn}, b.burn, "invalid burn value") +} + +func TestHandleConfig(t *testing.T) { + t.Parallel() + es := &testEpochState{ + epochCounter: 100, + } + nm := &testNetmapClient{} + m := &testMorphClient{ + balance: 150, + } + proc, err := newTestProc(t, func(p *Params) { + p.NetmapClient = nm + p.MorphClient = m + p.EpochState = es + }) + require.NoError(t, err, "failed to create processor") + + ev := frostfsEvent.Config{ + IDValue: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, + KeyValue: []byte{1, 2, 3, 4, 5}, + ValueValue: []byte{6, 7, 8, 9, 0}, + TxHashValue: util.Uint256{100}, + } + + proc.handleConfig(ev) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + var expConfig nmClient.SetConfigPrm + expConfig.SetHash(ev.TxHashValue) + expConfig.SetID(ev.IDValue) + expConfig.SetKey(ev.KeyValue) + expConfig.SetValue(ev.ValueValue) + + require.EqualValues(t, []nmClient.SetConfigPrm{expConfig}, nm.config, "invalid config value") +} + +func TestHandleUnbind(t *testing.T) { + t.Parallel() + es := &testEpochState{ + epochCounter: 100, + } + m := &testMorphClient{ + balance: 150, + } + id := &testIDClient{} + proc, err := newTestProc(t, func(p *Params) { + p.EpochState = es + p.MorphClient = m + p.FrostFSIDClient = id + }) + require.NoError(t, err, "failed to create processor") + + p, err := keys.NewPrivateKey() + require.NoError(t, err) + + evUnbind := frostfsEvent.Unbind{ + BindCommon: frostfsEvent.BindCommon{ + UserValue: util.Uint160{49}.BytesBE(), + KeysValue: [][]byte{ + p.PublicKey().Bytes(), + }, + TxHashValue: util.Uint256{100}, + }, + } + + proc.handleUnbind(evUnbind) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + var userID user.ID + userID.SetScriptHash(util.Uint160{49}) + + var expBind frostfsid.CommonBindPrm + expBind.SetOwnerID(userID.WalletBytes()) + expBind.SetKeys(evUnbind.BindCommon.KeysValue) + expBind.SetHash(evUnbind.BindCommon.TxHashValue) + + var expNilSlice []frostfsid.CommonBindPrm + + require.EqualValues(t, []frostfsid.CommonBindPrm{expBind}, id.remove, "invalid remove keys value") + require.EqualValues(t, expNilSlice, id.add, "invalid add keys value") + + evBind := frostfsEvent.Bind{ + BindCommon: frostfsEvent.BindCommon{ + UserValue: util.Uint160{49}.BytesBE(), + KeysValue: [][]byte{ + p.PublicKey().Bytes(), + }, + TxHashValue: util.Uint256{100}, + }, + } + + proc.handleBind(evBind) + + time.Sleep(time.Second) + + require.EqualValues(t, []frostfsid.CommonBindPrm{expBind}, id.remove, "invalid remove keys value") + require.EqualValues(t, []frostfsid.CommonBindPrm{expBind}, id.add, "invalid add keys value") +} + +func newTestProc(t *testing.T, nonDefault func(p *Params)) (*Processor, error) { + p := &Params{ + Log: test.NewLogger(t, true), + PoolSize: 1, + FrostFSContract: util.Uint160{0}, + FrostFSIDClient: &testIDClient{}, + BalanceClient: &testBalaceClient{}, + NetmapClient: &testNetmapClient{}, + MorphClient: &testMorphClient{}, + EpochState: &testEpochState{}, + AlphabetState: &testAlphabetState{isAlphabet: true}, + Converter: &testPrecisionConverter{}, + MintEmitCacheSize: 100, + MintEmitThreshold: 10, + MintEmitValue: fixedn.Fixed8(50), + GasBalanceThreshold: 50, + } + + nonDefault(p) + + return New(p) +} + +type testEpochState struct { + epochCounter uint64 +} + +func (s *testEpochState) EpochCounter() uint64 { + return s.epochCounter +} + +type testAlphabetState struct { + isAlphabet bool +} + +func (s *testAlphabetState) IsAlphabet() bool { + return s.isAlphabet +} + +type testPrecisionConverter struct { +} + +func (c *testPrecisionConverter) ToBalancePrecision(v int64) int64 { + return v +} + +type testBalaceClient struct { + mint []balance.MintPrm + lock []balance.LockPrm + burn []balance.BurnPrm +} + +func (c *testBalaceClient) Mint(p balance.MintPrm) error { + c.mint = append(c.mint, p) + return nil +} +func (c *testBalaceClient) Lock(p balance.LockPrm) error { + c.lock = append(c.lock, p) + return nil +} +func (c *testBalaceClient) Burn(p balance.BurnPrm) error { + c.burn = append(c.burn, p) + return nil +} + +type testNetmapClient struct { + config []nmClient.SetConfigPrm +} + +func (c *testNetmapClient) SetConfig(p nmClient.SetConfigPrm) error { + c.config = append(c.config, p) + return nil +} + +type transferGas struct { + receiver util.Uint160 + amount fixedn.Fixed8 +} + +type testMorphClient struct { + balance int64 + transferGas []transferGas +} + +func (c *testMorphClient) GasBalance() (res int64, err error) { + return c.balance, nil +} +func (c *testMorphClient) TransferGas(receiver util.Uint160, amount fixedn.Fixed8) error { + c.transferGas = append(c.transferGas, transferGas{ + receiver: receiver, + amount: amount, + }) + return nil +} + +type testIDClient struct { + add []frostfsid.CommonBindPrm + remove []frostfsid.CommonBindPrm +} + +func (c *testIDClient) AddKeys(p frostfsid.CommonBindPrm) error { + c.add = append(c.add, p) + return nil +} + +func (c *testIDClient) RemoveKeys(args frostfsid.CommonBindPrm) error { + c.remove = append(c.remove, args) + return nil +} diff --git a/pkg/innerring/processors/frostfs/process_assets.go b/pkg/innerring/processors/frostfs/process_assets.go index e066975f7..cfbf21b08 100644 --- a/pkg/innerring/processors/frostfs/process_assets.go +++ b/pkg/innerring/processors/frostfs/process_assets.go @@ -15,7 +15,7 @@ const ( // Process deposit event by invoking a balance contract and sending native // gas in the sidechain. -func (np *Processor) processDeposit(deposit *frostfsEvent.Deposit) { +func (np *Processor) processDeposit(deposit frostfsEvent.Deposit) { if !np.alphabetState.IsAlphabet() { np.log.Info(logs.FrostFSNonAlphabetModeIgnoreDeposit) return @@ -80,7 +80,7 @@ func (np *Processor) processDeposit(deposit *frostfsEvent.Deposit) { } // Process withdraw event by locking assets in the balance account. -func (np *Processor) processWithdraw(withdraw *frostfsEvent.Withdraw) { +func (np *Processor) processWithdraw(withdraw frostfsEvent.Withdraw) { if !np.alphabetState.IsAlphabet() { np.log.Info(logs.FrostFSNonAlphabetModeIgnoreWithdraw) return @@ -111,7 +111,7 @@ func (np *Processor) processWithdraw(withdraw *frostfsEvent.Withdraw) { // Process cheque event by transferring assets from the lock account back to // the reserve account. -func (np *Processor) processCheque(cheque *frostfsEvent.Cheque) { +func (np *Processor) processCheque(cheque frostfsEvent.Cheque) { if !np.alphabetState.IsAlphabet() { np.log.Info(logs.FrostFSNonAlphabetModeIgnoreCheque) return diff --git a/pkg/innerring/processors/frostfs/process_bind.go b/pkg/innerring/processors/frostfs/process_bind.go index c5f8a930e..a9b523a77 100644 --- a/pkg/innerring/processors/frostfs/process_bind.go +++ b/pkg/innerring/processors/frostfs/process_bind.go @@ -6,7 +6,6 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfsid" - "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/frostfs" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/util" @@ -19,7 +18,7 @@ type bindCommon interface { TxHash() util.Uint256 } -func (np *Processor) processBind(e bindCommon) { +func (np *Processor) processBind(e bindCommon, bind bool) { if !np.alphabetState.IsAlphabet() { np.log.Info(logs.FrostFSNonAlphabetModeIgnoreBind) return @@ -27,10 +26,9 @@ func (np *Processor) processBind(e bindCommon) { c := &bindCommonContext{ bindCommon: e, + bind: bind, } - _, c.bind = e.(frostfs.Bind) - err := np.checkBindCommon(c) if err != nil { np.log.Error(logs.FrostFSInvalidManageKeyEvent, diff --git a/pkg/innerring/processors/frostfs/process_config.go b/pkg/innerring/processors/frostfs/process_config.go index 471edb9b7..ce2dabfdd 100644 --- a/pkg/innerring/processors/frostfs/process_config.go +++ b/pkg/innerring/processors/frostfs/process_config.go @@ -9,7 +9,7 @@ import ( // Process config event by setting configuration value from the mainchain in // the sidechain. -func (np *Processor) processConfig(config *frostfsEvent.Config) { +func (np *Processor) processConfig(config frostfsEvent.Config) { if !np.alphabetState.IsAlphabet() { np.log.Info(logs.FrostFSNonAlphabetModeIgnoreConfig) return diff --git a/pkg/innerring/processors/frostfs/processor.go b/pkg/innerring/processors/frostfs/processor.go index 42362eeed..2af15a814 100644 --- a/pkg/innerring/processors/frostfs/processor.go +++ b/pkg/innerring/processors/frostfs/processor.go @@ -6,7 +6,6 @@ import ( "sync" "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" - "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/balance" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfsid" nmClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap" @@ -36,14 +35,34 @@ type ( ToBalancePrecision(int64) int64 } + BalanceClient interface { + Mint(p balance.MintPrm) error + Lock(p balance.LockPrm) error + Burn(p balance.BurnPrm) error + } + + NetmapClient interface { + SetConfig(p nmClient.SetConfigPrm) error + } + + MorphClient interface { + GasBalance() (res int64, err error) + TransferGas(receiver util.Uint160, amount fixedn.Fixed8) error + } + + IDClient interface { + AddKeys(p frostfsid.CommonBindPrm) error + RemoveKeys(args frostfsid.CommonBindPrm) error + } + // Processor of events produced by frostfs contract in main net. Processor struct { log *logger.Logger pool *ants.Pool frostfsContract util.Uint160 - balanceClient *balance.Client - netmapClient *nmClient.Client - morphClient *client.Client + balanceClient BalanceClient + netmapClient NetmapClient + morphClient MorphClient epochState EpochState alphabetState AlphabetState converter PrecisionConverter @@ -52,8 +71,7 @@ type ( mintEmitThreshold uint64 mintEmitValue fixedn.Fixed8 gasBalanceThreshold int64 - - frostfsIDClient *frostfsid.Client + frostfsIDClient IDClient } // Params of the processor constructor. @@ -61,10 +79,10 @@ type ( Log *logger.Logger PoolSize int FrostFSContract util.Uint160 - FrostFSIDClient *frostfsid.Client - BalanceClient *balance.Client - NetmapClient *nmClient.Client - MorphClient *client.Client + FrostFSIDClient IDClient + BalanceClient BalanceClient + NetmapClient NetmapClient + MorphClient MorphClient EpochState EpochState AlphabetState AlphabetState Converter PrecisionConverter diff --git a/pkg/innerring/processors/governance/handlers_test.go b/pkg/innerring/processors/governance/handlers_test.go new file mode 100644 index 000000000..23f91869a --- /dev/null +++ b/pkg/innerring/processors/governance/handlers_test.go @@ -0,0 +1,304 @@ +package governance + +import ( + "encoding/binary" + "sort" + "testing" + "time" + + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" + frostfscontract "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfs" + nmClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/rolemanagement" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test" + "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/stretchr/testify/require" +) + +func TestHandleAlphabetSyncEvent(t *testing.T) { + t.Parallel() + testKeys := generateTestKeys(t) + + es := &testEpochState{ + epoch: 100, + } + as := &testAlphabetState{ + isAlphabet: true, + } + v := &testVoter{} + irf := &testIRFetcher{ + publicKeys: testKeys.sidechainKeys, + } + m := &testMorphClient{ + commiteeKeys: testKeys.sidechainKeys, + } + mn := &testMainnetClient{ + alphabetKeys: testKeys.mainnetKeys, + } + f := &testFrostFSClient{} + nm := &testNetmapClient{} + + proc, err := New( + &Params{ + Log: test.NewLogger(t, true), + EpochState: es, + AlphabetState: as, + Voter: v, + IRFetcher: irf, + NotaryDisabled: true, + MorphClient: m, + MainnetClient: mn, + FrostFSClient: f, + NetmapClient: nm, + }, + ) + + require.NoError(t, err, "failed to create processor") + + ev := Sync{ + txHash: util.Uint256{100}, + } + + proc.HandleAlphabetSync(ev) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + require.EqualValues(t, []VoteValidatorPrm{ + { + Validators: testKeys.newAlphabetExp, + Hash: &ev.txHash, + }, + }, v.votes, "invalid vote calls") + + var irUpdateExp nmClient.UpdateIRPrm + irUpdateExp.SetKeys(testKeys.newInnerRingExp) + irUpdateExp.SetHash(ev.txHash) + + require.EqualValues(t, []nmClient.UpdateIRPrm{irUpdateExp}, nm.updates, "invalid IR updates") + + var expAlphabetUpdates []client.UpdateAlphabetListPrm + require.EqualValues(t, expAlphabetUpdates, m.alphabetUpdates, "invalid alphabet updates") + + var expNotaryUpdates []client.UpdateNotaryListPrm + require.EqualValues(t, expNotaryUpdates, m.notaryUpdates, "invalid notary list updates") + + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, es.epoch) + + id := append([]byte(alphabetUpdateIDPrefix), buf...) + var expFrostFSAlphabetUpd frostfscontract.AlphabetUpdatePrm + expFrostFSAlphabetUpd.SetID(id) + expFrostFSAlphabetUpd.SetPubs(testKeys.newAlphabetExp) + + require.EqualValues(t, []frostfscontract.AlphabetUpdatePrm{expFrostFSAlphabetUpd}, f.updates, "invalid FrostFS alphabet updates") +} + +func TestHandleAlphabetDesignateEvent(t *testing.T) { + t.Parallel() + testKeys := generateTestKeys(t) + + es := &testEpochState{ + epoch: 100, + } + as := &testAlphabetState{ + isAlphabet: true, + } + v := &testVoter{} + irf := &testIRFetcher{ + publicKeys: testKeys.sidechainKeys, + } + m := &testMorphClient{ + commiteeKeys: testKeys.sidechainKeys, + } + mn := &testMainnetClient{ + alphabetKeys: testKeys.mainnetKeys, + } + f := &testFrostFSClient{} + nm := &testNetmapClient{} + + proc, err := New( + &Params{ + Log: test.NewLogger(t, true), + EpochState: es, + AlphabetState: as, + Voter: v, + IRFetcher: irf, + NotaryDisabled: false, + MorphClient: m, + MainnetClient: mn, + FrostFSClient: f, + NetmapClient: nm, + }, + ) + + require.NoError(t, err, "failed to create processor") + + ev := rolemanagement.Designate{ + TxHash: util.Uint256{100}, + Role: noderoles.NeoFSAlphabet, + } + + proc.HandleAlphabetSync(ev) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + require.EqualValues(t, []VoteValidatorPrm{ + { + Validators: testKeys.newAlphabetExp, + Hash: &ev.TxHash, + }, + }, v.votes, "invalid vote calls") + + var irUpdatesExp []nmClient.UpdateIRPrm + require.EqualValues(t, irUpdatesExp, nm.updates, "invalid IR updates") + + var alpabetUpdExp client.UpdateAlphabetListPrm + alpabetUpdExp.SetList(testKeys.newInnerRingExp) + alpabetUpdExp.SetHash(ev.TxHash) + require.EqualValues(t, []client.UpdateAlphabetListPrm{alpabetUpdExp}, m.alphabetUpdates, "invalid alphabet updates") + + var expNotaryUpdate client.UpdateNotaryListPrm + expNotaryUpdate.SetList(testKeys.newAlphabetExp) + expNotaryUpdate.SetHash(ev.TxHash) + require.EqualValues(t, []client.UpdateNotaryListPrm{expNotaryUpdate}, m.notaryUpdates, "invalid notary list updates") + + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, es.epoch) + + id := append([]byte(alphabetUpdateIDPrefix), buf...) + var expFrostFSAlphabetUpd frostfscontract.AlphabetUpdatePrm + expFrostFSAlphabetUpd.SetID(id) + expFrostFSAlphabetUpd.SetPubs(testKeys.newAlphabetExp) + + require.EqualValues(t, []frostfscontract.AlphabetUpdatePrm{expFrostFSAlphabetUpd}, f.updates, "invalid FrostFS alphabet updates") +} + +type testKeys struct { + sidechainKeys keys.PublicKeys + mainnetKeys keys.PublicKeys + newAlphabetExp keys.PublicKeys + newInnerRingExp keys.PublicKeys +} + +func generateTestKeys(t *testing.T) testKeys { + for { + var result testKeys + + for i := 0; i < 4; i++ { + pk, err := keys.NewPrivateKey() + require.NoError(t, err, "failed to create private key") + result.sidechainKeys = append(result.sidechainKeys, pk.PublicKey()) + } + + result.mainnetKeys = append(result.mainnetKeys, result.sidechainKeys...) + pk, err := keys.NewPrivateKey() + require.NoError(t, err, "failed to create private key") + result.mainnetKeys = append(result.mainnetKeys, pk.PublicKey()) + + result.newAlphabetExp, err = newAlphabetList(result.sidechainKeys, result.mainnetKeys) + require.NoError(t, err, "failed to create expected new alphabet") + + if len(result.newAlphabetExp) == 0 { + continue //can be happen because of random and sort + } + + var irKeys keys.PublicKeys + irKeys = append(irKeys, result.sidechainKeys...) + result.newInnerRingExp, err = updateInnerRing(irKeys, result.sidechainKeys, result.newAlphabetExp) + require.NoError(t, err, "failed to create expected new IR") + sort.Sort(result.newInnerRingExp) + + return result + } +} + +type testEpochState struct { + epoch uint64 +} + +func (s *testEpochState) EpochCounter() uint64 { + return s.epoch +} + +type testAlphabetState struct { + isAlphabet bool +} + +func (s *testAlphabetState) IsAlphabet() bool { + return s.isAlphabet +} + +type testVoter struct { + votes []VoteValidatorPrm +} + +func (v *testVoter) VoteForSidechainValidator(prm VoteValidatorPrm) error { + v.votes = append(v.votes, prm) + return nil +} + +type testIRFetcher struct { + publicKeys keys.PublicKeys +} + +func (f *testIRFetcher) InnerRingKeys() (keys.PublicKeys, error) { + return f.publicKeys, nil +} + +type testMorphClient struct { + commiteeKeys keys.PublicKeys + + alphabetUpdates []client.UpdateAlphabetListPrm + notaryUpdates []client.UpdateNotaryListPrm +} + +func (c *testMorphClient) Committee() (res keys.PublicKeys, err error) { + return c.commiteeKeys, nil +} + +func (c *testMorphClient) UpdateNeoFSAlphabetList(prm client.UpdateAlphabetListPrm) error { + c.alphabetUpdates = append(c.alphabetUpdates, prm) + return nil +} + +func (c *testMorphClient) UpdateNotaryList(prm client.UpdateNotaryListPrm) error { + c.notaryUpdates = append(c.notaryUpdates, prm) + return nil +} + +type testMainnetClient struct { + alphabetKeys keys.PublicKeys + designateHash util.Uint160 +} + +func (c *testMainnetClient) NeoFSAlphabetList() (res keys.PublicKeys, err error) { + return c.alphabetKeys, nil +} + +func (c *testMainnetClient) GetDesignateHash() util.Uint160 { + return c.designateHash +} + +type testFrostFSClient struct { + updates []frostfscontract.AlphabetUpdatePrm +} + +func (c *testFrostFSClient) AlphabetUpdate(p frostfscontract.AlphabetUpdatePrm) error { + c.updates = append(c.updates, p) + return nil +} + +type testNetmapClient struct { + updates []nmClient.UpdateIRPrm +} + +func (c *testNetmapClient) UpdateInnerRing(p nmClient.UpdateIRPrm) error { + c.updates = append(c.updates, p) + return nil +} diff --git a/pkg/innerring/processors/governance/processor.go b/pkg/innerring/processors/governance/processor.go index b65dd17b7..e08bd3809 100644 --- a/pkg/innerring/processors/governance/processor.go +++ b/pkg/innerring/processors/governance/processor.go @@ -53,20 +53,39 @@ type ( InnerRingKeys() (keys.PublicKeys, error) } + FrostFSClient interface { + AlphabetUpdate(p frostfscontract.AlphabetUpdatePrm) error + } + + NetmapClient interface { + UpdateInnerRing(p nmClient.UpdateIRPrm) error + } + + MainnetClient interface { + NeoFSAlphabetList() (res keys.PublicKeys, err error) + GetDesignateHash() util.Uint160 + } + + MorphClient interface { + Committee() (res keys.PublicKeys, err error) + UpdateNeoFSAlphabetList(prm client.UpdateAlphabetListPrm) error + UpdateNotaryList(prm client.UpdateNotaryListPrm) error + } + // Processor of events related to governance in the network. Processor struct { log *logger.Logger pool *ants.Pool - frostfsClient *frostfscontract.Client - netmapClient *nmClient.Client + frostfsClient FrostFSClient + netmapClient NetmapClient alphabetState AlphabetState epochState EpochState voter Voter irFetcher IRFetcher - mainnetClient *client.Client - morphClient *client.Client + mainnetClient MainnetClient + morphClient MorphClient notaryDisabled bool @@ -82,10 +101,10 @@ type ( Voter Voter IRFetcher IRFetcher - MorphClient *client.Client - MainnetClient *client.Client - FrostFSClient *frostfscontract.Client - NetmapClient *nmClient.Client + MorphClient MorphClient + MainnetClient MainnetClient + FrostFSClient FrostFSClient + NetmapClient NetmapClient NotaryDisabled bool } diff --git a/pkg/innerring/processors/netmap/handlers_test.go b/pkg/innerring/processors/netmap/handlers_test.go new file mode 100644 index 000000000..576d6c5dd --- /dev/null +++ b/pkg/innerring/processors/netmap/handlers_test.go @@ -0,0 +1,570 @@ +package netmap + +import ( + "fmt" + "testing" + "time" + + v2netmap "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap" + netmapContract "git.frostfs.info/TrueCloudLab/frostfs-contract/netmap" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring/processors/audit" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring/processors/governance" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring/processors/settlement" + timerEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring/timers" + cntClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container" + netmapclient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event" + netmapEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/netmap" + "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/core/transaction" + "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/network/payload" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/stretchr/testify/require" +) + +func TestNewEpochTick(t *testing.T) { + t.Parallel() + es := &testEpochState{ + counter: 100, + } + nc := &testNetmapClient{} + + proc, err := newTestProc(t, func(p *Params) { + p.NotaryDisabled = true + p.CleanupEnabled = true + p.EpochState = es + p.NetmapClient = nc + }) + + require.NoError(t, err, "failed to create processor") + + ev := timerEvent.NewEpochTick{} + proc.HandleNewEpochTick(ev) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + require.EqualValues(t, []uint64{101}, nc.newEpochs, "invalid epochs") +} + +func TestNewEpoch(t *testing.T) { + t.Parallel() + 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()) + + network := &netmap.NetMap{} + network.SetNodes([]netmap.NodeInfo{node1, node2}) + + es := &testEpochState{ + counter: 100, + duration: 10, + } + r := &testEpochResetter{} + cc := &testContainerClient{} + nc := &testNetmapClient{ + epochDuration: 20, + txHeights: map[util.Uint256]uint32{ + {101}: 10_000, + }, + netmap: network, + } + eh := &testEventHandler{} + + proc, err := newTestProc(t, func(p *Params) { + p.NotaryDisabled = true + p.NotaryDepositHandler = eh.Handle + p.HandleAudit = eh.Handle + p.AuditSettlementsHandler = eh.Handle + p.AlphabetSyncHandler = eh.Handle + p.NetmapClient = nc + p.ContainerWrapper = cc + p.EpochTimer = r + p.EpochState = es + }) + + require.NoError(t, err, "failed to create processor") + + ev := netmapEvent.NewEpoch{ + Num: 101, + Hash: util.Uint256{101}, + } + proc.handleNewEpoch(ev) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + require.Equal(t, nc.epochDuration, es.duration, "invalid epoch duration") + require.Equal(t, ev.Num, es.counter, "invalid epoch counter") + require.EqualValues(t, []uint32{nc.txHeights[ev.Hash]}, r.timers, "invalid epoch timer resets") + + var expEstimation cntClient.StartEstimationPrm + expEstimation.SetEpoch(ev.Num - 1) + expEstimation.SetHash(ev.Hash) + require.EqualValues(t, []cntClient.StartEstimationPrm{expEstimation}, cc.estimations, "invalid estimations") + + require.EqualValues(t, []event.Event{ + audit.NewAuditStartEvent(ev.Num), + settlement.NewAuditEvent(ev.Num), + governance.NewSyncEvent(ev.TxHash()), + ev, + }, eh.handledEvents, "invalid handled events") +} + +func TestAddPeer(t *testing.T) { + t.Parallel() + + t.Run("with notary", func(t *testing.T) { + t.Parallel() + nc := &testNetmapClient{ + contractAddress: util.Uint160{47}, + } + + proc, err := newTestProc(t, func(p *Params) { + p.NotaryDisabled = true + p.NetmapClient = nc + }) + + require.NoError(t, err, "failed to create processor") + + var node netmap.NodeInfo + key, err := keys.NewPublicKeyFromString("038c862959e56b43e20f79187c4fe9e0bc7c8c66c1603e6cf0ec7f87ab6b08dc35") + require.NoError(t, err, "failed to parse key1") + node.SetPublicKey(key.Bytes()) + + ev := netmapEvent.AddPeer{ + NodeBytes: node.Marshal(), + Request: &payload.P2PNotaryRequest{ + MainTransaction: &transaction.Transaction{ + Nonce: 100, + }, + }, + } + proc.handleAddPeer(ev) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + require.EqualValues(t, []notaryInvoke{ + { + contract: nc.contractAddress, + fee: 0, + nonce: ev.Request.MainTransaction.Nonce, + vub: nil, + method: "addPeerIR", + args: []any{ev.Node()}, + }, + }, nc.notaryInvokes, "invalid notary invokes") + }) + + t.Run("without notary", func(t *testing.T) { + t.Parallel() + + nc := &testNetmapClient{ + contractAddress: util.Uint160{47}, + } + + proc, err := newTestProc(t, func(p *Params) { + p.NotaryDisabled = true + p.NetmapClient = nc + }) + + require.NoError(t, err, "failed to create processor") + + var node netmap.NodeInfo + key, err := keys.NewPublicKeyFromString("038c862959e56b43e20f79187c4fe9e0bc7c8c66c1603e6cf0ec7f87ab6b08dc35") + require.NoError(t, err, "failed to parse key") + node.SetPublicKey(key.Bytes()) + + ev := netmapEvent.AddPeer{ + NodeBytes: node.Marshal(), + } + proc.handleAddPeer(ev) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + var addPeerExp netmapclient.AddPeerPrm + addPeerExp.SetNodeInfo(node) + require.EqualValues(t, []netmapclient.AddPeerPrm{addPeerExp}, nc.addPeers, "invalid peers") + }) +} + +func TestUpdateState(t *testing.T) { + t.Parallel() + + t.Run("with notary", func(t *testing.T) { + t.Parallel() + ns := &testNodeStateSettings{ + maintAllowed: true, + } + nc := &testNetmapClient{} + + proc, err := newTestProc(t, func(p *Params) { + p.NotaryDisabled = true + p.NodeStateSettings = ns + p.NetmapClient = nc + }) + + require.NoError(t, err, "failed to create processor") + + key, err := keys.NewPublicKeyFromString("038c862959e56b43e20f79187c4fe9e0bc7c8c66c1603e6cf0ec7f87ab6b08dc35") + require.NoError(t, err, "failed to parse key") + + ev := netmapEvent.UpdatePeer{ + State: netmapContract.NodeStateOnline, + PubKey: key, + Request: &payload.P2PNotaryRequest{ + MainTransaction: &transaction.Transaction{ + Nonce: 100, + }, + }, + } + proc.handleUpdateState(ev) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + require.EqualValues(t, []*transaction.Transaction{ + ev.Request.MainTransaction, + }, nc.invokedTxs, "invalid invoked transactions") + }) + + t.Run("without notary", func(t *testing.T) { + t.Parallel() + ns := &testNodeStateSettings{ + maintAllowed: true, + } + nc := &testNetmapClient{} + + proc, err := newTestProc(t, func(p *Params) { + p.NetmapClient = nc + p.NodeStateSettings = ns + }) + + require.NoError(t, err, "failed to create processor") + + key, err := keys.NewPublicKeyFromString("038c862959e56b43e20f79187c4fe9e0bc7c8c66c1603e6cf0ec7f87ab6b08dc35") + require.NoError(t, err, "failed to parse key") + + ev := netmapEvent.UpdatePeer{ + State: netmapContract.NodeStateOnline, + PubKey: key, + } + proc.handleUpdateState(ev) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + var expUpdPeer netmapclient.UpdatePeerPrm + expUpdPeer.SetMaintenance() + expUpdPeer.SetOnline() + expUpdPeer.SetKey(ev.PubKey.Bytes()) + + require.EqualValues(t, []netmapclient.UpdatePeerPrm{expUpdPeer}, nc.peerStateUpdates, "invalid peer state updates") + }) +} + +func TestCleanupTick(t *testing.T) { + t.Parallel() + + t.Run("notary disabled", func(t *testing.T) { + t.Parallel() + + nc := &testNetmapClient{} + + proc, err := newTestProc(t, func(p *Params) { + p.NetmapClient = nc + p.NotaryDisabled = true + p.CleanupEnabled = true + }) + + require.NoError(t, err, "failed to create processor") + + key1Str := "038c862959e56b43e20f79187c4fe9e0bc7c8c66c1603e6cf0ec7f87ab6b08dc35" + proc.netmapSnapshot.lastAccess[key1Str] = epochStampWithNodeInfo{ + epochStamp: epochStamp{ + epoch: 95, + removeFlag: false, + }, + } + key2Str := "02ac920cd7df0b61b289072e6b946e2da4e1a31b9ab1c621bb475e30fa4ab102c3" + proc.netmapSnapshot.lastAccess[key2Str] = epochStampWithNodeInfo{ + epochStamp: epochStamp{ + epoch: 98, + removeFlag: false, + }, + } + + ev := netmapCleanupTick{ + epoch: 100, + txHash: util.Uint256{123}, + } + + proc.handleCleanupTick(ev) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + keyExp, err := keys.NewPublicKeyFromString(key1Str) + require.NoError(t, err, "failed to parse expired key") + + updExp := netmapclient.UpdatePeerPrm{} + updExp.SetKey(keyExp.Bytes()) + updExp.SetHash(ev.TxHash()) + + require.EqualValues(t, []netmapclient.UpdatePeerPrm{updExp}, nc.peerStateUpdates, "invalid peer updates") + require.True(t, proc.netmapSnapshot.lastAccess[key1Str].removeFlag, "invalid expired removed flag") + require.False(t, proc.netmapSnapshot.lastAccess[key2Str].removeFlag, "invalid non expired removed flag") + }) + + t.Run("notary enabled", func(t *testing.T) { + t.Parallel() + + nc := &testNetmapClient{ + contractAddress: util.Uint160{111}, + } + proc, err := newTestProc(t, + func(p *Params) { + p.NetmapClient = nc + p.CleanupEnabled = true + }, + ) + + require.NoError(t, err, "failed to create processor") + + key1Str := "038c862959e56b43e20f79187c4fe9e0bc7c8c66c1603e6cf0ec7f87ab6b08dc35" + proc.netmapSnapshot.lastAccess[key1Str] = epochStampWithNodeInfo{ + epochStamp: epochStamp{ + epoch: 95, + removeFlag: false, + }, + } + key2Str := "02ac920cd7df0b61b289072e6b946e2da4e1a31b9ab1c621bb475e30fa4ab102c3" + proc.netmapSnapshot.lastAccess[key2Str] = epochStampWithNodeInfo{ + epochStamp: epochStamp{ + epoch: 98, + removeFlag: false, + }, + } + + ev := netmapCleanupTick{ + epoch: 100, + txHash: util.Uint256{123}, + } + + proc.handleCleanupTick(ev) + + for proc.pool.Running() > 0 { + time.Sleep(10 * time.Millisecond) + } + + keyExp, err := keys.NewPublicKeyFromString(key1Str) + require.NoError(t, err, "failed to parse expired key") + + updExp := netmapclient.UpdatePeerPrm{} + updExp.SetKey(keyExp.Bytes()) + updExp.SetHash(ev.TxHash()) + + require.EqualValues(t, []notaryInvoke{ + { + contract: nc.contractAddress, + fee: 0, + nonce: uint32(ev.epoch), + vub: nil, + method: "updateStateIR", + args: []any{int64(v2netmap.Offline), keyExp.Bytes()}, + }, + }, nc.notaryInvokes, "invalid notary invokes") + require.True(t, proc.netmapSnapshot.lastAccess[key1Str].removeFlag, "invalid expired removed flag") + require.False(t, proc.netmapSnapshot.lastAccess[key2Str].removeFlag, "invalid non expired removed flag") + }) +} + +func newTestProc(t *testing.T, nonDefault func(p *Params)) (*Processor, error) { + ns := &testNodeStateSettings{} + es := &testEpochState{} + r := &testEpochResetter{} + as := &testAlphabetState{ + isAlphabet: true, + } + cc := &testContainerClient{} + nc := &testNetmapClient{} + eh := &testEventHandler{} + + p := &Params{ + Log: test.NewLogger(t, true), + PoolSize: 1, + CleanupEnabled: false, + CleanupThreshold: 3, + NotaryDisabled: false, + NodeStateSettings: ns, + NodeValidator: &testValidator{}, + EpochState: es, + EpochTimer: r, + AlphabetState: as, + ContainerWrapper: cc, + NetmapClient: nc, + NotaryDepositHandler: eh.Handle, + HandleAudit: eh.Handle, + AuditSettlementsHandler: eh.Handle, + AlphabetSyncHandler: eh.Handle, + } + + nonDefault(p) + + return New(p) +} + +type testNodeStateSettings struct { + maintAllowed bool +} + +func (s *testNodeStateSettings) MaintenanceModeAllowed() error { + if s.maintAllowed { + return nil + } + return fmt.Errorf("maintenance mode not allowed") +} + +type testValidator struct{} + +func (v *testValidator) VerifyAndUpdate(*netmap.NodeInfo) error { + return nil +} + +type testEpochState struct { + counter uint64 + duration uint64 +} + +func (s *testEpochState) SetEpochCounter(c uint64) { + s.counter = c +} +func (s *testEpochState) EpochCounter() uint64 { + return s.counter +} +func (s *testEpochState) SetEpochDuration(d uint64) { + s.duration = d +} +func (s *testEpochState) EpochDuration() uint64 { + return s.duration +} + +type testEpochResetter struct { + timers []uint32 +} + +func (r *testEpochResetter) ResetEpochTimer(t uint32) error { + r.timers = append(r.timers, t) + return nil +} + +type testAlphabetState struct { + isAlphabet bool +} + +func (s *testAlphabetState) IsAlphabet() bool { + return s.isAlphabet +} + +type testContainerClient struct { + estimations []cntClient.StartEstimationPrm +} + +func (c *testContainerClient) StartEstimation(p cntClient.StartEstimationPrm) error { + c.estimations = append(c.estimations, p) + return nil +} + +type notaryInvoke struct { + contract util.Uint160 + fee fixedn.Fixed8 + nonce uint32 + vub *uint32 + method string + args []any +} + +type testNetmapClient struct { + contractAddress util.Uint160 + epochDuration uint64 + netmap *netmap.NetMap + txHeights map[util.Uint256]uint32 + + peerStateUpdates []netmapclient.UpdatePeerPrm + notaryInvokes []notaryInvoke + newEpochs []uint64 + addPeers []netmapclient.AddPeerPrm + invokedTxs []*transaction.Transaction +} + +func (c *testNetmapClient) UpdatePeerState(p netmapclient.UpdatePeerPrm) error { + c.peerStateUpdates = append(c.peerStateUpdates, p) + return nil +} +func (c *testNetmapClient) MorphNotaryInvoke(contract util.Uint160, fee fixedn.Fixed8, nonce uint32, vub *uint32, method string, args ...any) error { + c.notaryInvokes = append(c.notaryInvokes, notaryInvoke{ + contract: contract, + fee: fee, + nonce: nonce, + vub: vub, + method: method, + args: args, + }) + return nil +} +func (c *testNetmapClient) ContractAddress() util.Uint160 { + return c.contractAddress +} +func (c *testNetmapClient) EpochDuration() (uint64, error) { + return c.epochDuration, nil +} +func (c *testNetmapClient) MorphTxHeight(h util.Uint256) (uint32, error) { + if res, found := c.txHeights[h]; found { + return res, nil + } + return 0, fmt.Errorf("not found") +} +func (c *testNetmapClient) NetMap() (*netmap.NetMap, error) { + return c.netmap, nil +} +func (c *testNetmapClient) NewEpoch(epoch uint64, force bool) error { + c.newEpochs = append(c.newEpochs, epoch) + return nil +} +func (c *testNetmapClient) MorphIsValidScript(script []byte, signers []transaction.Signer) (valid bool, err error) { + return true, nil +} +func (c *testNetmapClient) AddPeer(p netmapclient.AddPeerPrm) error { + c.addPeers = append(c.addPeers, p) + return nil +} +func (c *testNetmapClient) MorphNotarySignAndInvokeTX(mainTx *transaction.Transaction) error { + c.invokedTxs = append(c.invokedTxs, mainTx) + return nil +} + +type testEventHandler struct { + handledEvents []event.Event +} + +func (h *testEventHandler) Handle(e event.Event) { + h.handledEvents = append(h.handledEvents, e) +} diff --git a/pkg/innerring/processors/netmap/process_cleanup.go b/pkg/innerring/processors/netmap/process_cleanup.go index d50c69c78..45a08b377 100644 --- a/pkg/innerring/processors/netmap/process_cleanup.go +++ b/pkg/innerring/processors/netmap/process_cleanup.go @@ -39,7 +39,7 @@ func (np *Processor) processNetmapCleanupTick(ev netmapCleanupTick) { err = np.netmapClient.UpdatePeerState(prm) } else { - err = np.netmapClient.Morph().NotaryInvoke( + err = np.netmapClient.MorphNotaryInvoke( np.netmapClient.ContractAddress(), 0, uint32(ev.epoch), diff --git a/pkg/innerring/processors/netmap/process_epoch.go b/pkg/innerring/processors/netmap/process_epoch.go index 17e445b13..35521b5ec 100644 --- a/pkg/innerring/processors/netmap/process_epoch.go +++ b/pkg/innerring/processors/netmap/process_epoch.go @@ -25,7 +25,7 @@ func (np *Processor) processNewEpoch(ev netmapEvent.NewEpoch) { np.epochState.SetEpochCounter(epoch) - h, err := np.netmapClient.Morph().TxHeight(ev.TxHash()) + h, err := np.netmapClient.MorphTxHeight(ev.TxHash()) if err != nil { np.log.Warn(logs.NetmapCantGetTransactionHeight, zap.String("hash", ev.TxHash().StringLE()), diff --git a/pkg/innerring/processors/netmap/process_peers.go b/pkg/innerring/processors/netmap/process_peers.go index bf54ed341..9e6eeb53e 100644 --- a/pkg/innerring/processors/netmap/process_peers.go +++ b/pkg/innerring/processors/netmap/process_peers.go @@ -21,7 +21,7 @@ func (np *Processor) processAddPeer(ev netmapEvent.AddPeer) { // check if notary transaction is valid, see #976 if originalRequest := ev.NotaryRequest(); originalRequest != nil { tx := originalRequest.MainTransaction - ok, err := np.netmapClient.Morph().IsValidScript(tx.Script, tx.Signers) + ok, err := np.netmapClient.MorphIsValidScript(tx.Script, tx.Signers) if err != nil || !ok { np.log.Warn(logs.NetmapNonhaltNotaryTransaction, zap.String("method", "netmap.AddPeer"), @@ -73,7 +73,7 @@ func (np *Processor) processAddPeer(ev netmapEvent.AddPeer) { if nr := ev.NotaryRequest(); nr != nil { // create new notary request with the original nonce - err = np.netmapClient.Morph().NotaryInvoke( + err = np.netmapClient.MorphNotaryInvoke( np.netmapClient.ContractAddress(), 0, nr.MainTransaction.Nonce, @@ -117,7 +117,7 @@ func (np *Processor) processUpdatePeer(ev netmapEvent.UpdatePeer) { } if nr := ev.NotaryRequest(); nr != nil { - err = np.netmapClient.Morph().NotarySignAndInvokeTX(nr.MainTransaction) + err = np.netmapClient.MorphNotarySignAndInvokeTX(nr.MainTransaction) } else { prm := netmapclient.UpdatePeerPrm{} diff --git a/pkg/innerring/processors/netmap/processor.go b/pkg/innerring/processors/netmap/processor.go index f6f490cec..373598326 100644 --- a/pkg/innerring/processors/netmap/processor.go +++ b/pkg/innerring/processors/netmap/processor.go @@ -6,13 +6,16 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring/processors/netmap/nodevalidation/state" - "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container" - nmClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap" + cntClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container" + netmapclient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event" netmapEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/netmap" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" "github.com/nspcc-dev/neo-go/pkg/core/mempoolevent" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "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" ) @@ -51,6 +54,23 @@ type ( VerifyAndUpdate(*netmap.NodeInfo) error } + Client interface { + UpdatePeerState(p netmapclient.UpdatePeerPrm) error + MorphNotaryInvoke(contract util.Uint160, fee fixedn.Fixed8, nonce uint32, vub *uint32, method string, args ...any) error + ContractAddress() util.Uint160 + EpochDuration() (uint64, error) + MorphTxHeight(h util.Uint256) (res uint32, err error) + NetMap() (*netmap.NetMap, error) + NewEpoch(epoch uint64, force bool) error + MorphIsValidScript(script []byte, signers []transaction.Signer) (valid bool, err error) + AddPeer(p netmapclient.AddPeerPrm) error + MorphNotarySignAndInvokeTX(mainTx *transaction.Transaction) error + } + + ContainerClient interface { + StartEstimation(p cntClient.StartEstimationPrm) error + } + // Processor of events produced by network map contract // and new epoch ticker, because it is related to contract. Processor struct { @@ -60,8 +80,8 @@ type ( epochState EpochState alphabetState AlphabetState - netmapClient *nmClient.Client - containerWrp *container.Client + netmapClient Client + containerWrp ContainerClient netmapSnapshot cleanupTable @@ -81,13 +101,13 @@ type ( Params struct { Log *logger.Logger PoolSize int - NetmapClient *nmClient.Client + NetmapClient Client EpochTimer EpochTimerReseter EpochState EpochState AlphabetState AlphabetState CleanupEnabled bool CleanupThreshold uint64 // in epochs - ContainerWrapper *container.Client + ContainerWrapper ContainerClient HandleAudit event.Handler AuditSettlementsHandler event.Handler diff --git a/pkg/innerring/processors/netmap/wrappers.go b/pkg/innerring/processors/netmap/wrappers.go new file mode 100644 index 000000000..255d498d3 --- /dev/null +++ b/pkg/innerring/processors/netmap/wrappers.go @@ -0,0 +1,59 @@ +package netmap + +import ( + netmapclient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" + "github.com/nspcc-dev/neo-go/pkg/util" +) + +func NewNetmapClient(netmapClient *netmapclient.Client) Client { + return &netmapClientWrapper{ + netmapClient: netmapClient, + } +} + +type netmapClientWrapper struct { + netmapClient *netmapclient.Client +} + +func (w *netmapClientWrapper) UpdatePeerState(p netmapclient.UpdatePeerPrm) error { + return w.netmapClient.UpdatePeerState(p) +} + +func (w *netmapClientWrapper) MorphNotaryInvoke(contract util.Uint160, fee fixedn.Fixed8, nonce uint32, vub *uint32, method string, args ...any) error { + return w.netmapClient.Morph().NotaryInvoke(contract, fee, nonce, vub, method, args...) +} + +func (w *netmapClientWrapper) ContractAddress() util.Uint160 { + return w.netmapClient.ContractAddress() +} + +func (w *netmapClientWrapper) EpochDuration() (uint64, error) { + return w.netmapClient.EpochDuration() +} + +func (w *netmapClientWrapper) MorphTxHeight(h util.Uint256) (res uint32, err error) { + return w.netmapClient.Morph().TxHeight(h) +} + +func (w *netmapClientWrapper) NetMap() (*netmap.NetMap, error) { + return w.netmapClient.NetMap() +} + +func (w *netmapClientWrapper) NewEpoch(epoch uint64, force bool) error { + return w.netmapClient.NewEpoch(epoch, force) +} + +func (w *netmapClientWrapper) MorphIsValidScript(script []byte, signers []transaction.Signer) (valid bool, err error) { + return w.netmapClient.Morph().IsValidScript(script, signers) +} + +func (w *netmapClientWrapper) AddPeer(p netmapclient.AddPeerPrm) error { + return w.netmapClient.AddPeer(p) +} + +func (w *netmapClientWrapper) MorphNotarySignAndInvokeTX(mainTx *transaction.Transaction) error { + return w.netmapClient.Morph().NotarySignAndInvokeTX(mainTx) +} diff --git a/pkg/innerring/state_test.go b/pkg/innerring/state_test.go new file mode 100644 index 000000000..fe09f8f2d --- /dev/null +++ b/pkg/innerring/state_test.go @@ -0,0 +1,53 @@ +package innerring + +import ( + "testing" + "time" + + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" + control "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/stretchr/testify/require" +) + +func TestServerState(t *testing.T) { + keyStr := "03ff65b6ae79134a4dce9d0d39d3851e9bab4ee97abf86e81e1c5bbc50cd2826ae" + commiteeKeys, err := keys.NewPublicKeysFromStrings([]string{keyStr}) + require.NoError(t, err, "convert string to commitee public keys failed") + cf := &testCommiteeFetcher{ + keys: commiteeKeys, + } + + irKeys, err := keys.NewPublicKeysFromStrings([]string{keyStr}) + require.NoError(t, err, "convert string to IR public keys failed") + irf := &testIRFetcher{ + keys: irKeys, + } + + key, err := keys.NewPublicKeyFromString(keyStr) + require.NoError(t, err, "convert string to public key failed") + + require.NoError(t, err, "failed to create morph client") + srv := &Server{ + statusIndex: newInnerRingIndexer(cf, irf, key, time.Second), + morphClient: &client.Client{}, + } + + var epoch uint64 = 100 + srv.SetEpochCounter(epoch) + require.Equal(t, epoch, srv.EpochCounter(), "invalid epoch counter") + + var epochDuration uint64 = 15 + srv.SetEpochDuration(epochDuration) + require.Equal(t, epochDuration, srv.EpochDuration(), "invalid epoch duration") + + var healthStatus control.HealthStatus = control.HealthStatus_READY + srv.setHealthStatus(healthStatus) + require.Equal(t, healthStatus, srv.HealthStatus(), "invalid health status") + + require.True(t, srv.IsActive(), "invalid IsActive result") + require.True(t, srv.IsAlphabet(), "invalid IsAlphabet result") + require.Equal(t, 0, srv.InnerRingIndex(), "invalid IR index") + require.Equal(t, 1, srv.InnerRingSize(), "invalid IR index") + require.Equal(t, 0, srv.AlphabetIndex(), "invalid alphabet index") +} diff --git a/pkg/morph/client/container/get.go b/pkg/morph/client/container/get.go index 4775cd281..009b22f3c 100644 --- a/pkg/morph/client/container/get.go +++ b/pkg/morph/client/container/get.go @@ -26,8 +26,12 @@ func AsContainerSource(w *Client) containercore.Source { return (*containerSource)(w) } +type getContainer interface { + Get(cid []byte) (*containercore.Container, error) +} + // Get marshals container ID, and passes it to Wrapper's Get method. -func Get(c *Client, cnr cid.ID) (*containercore.Container, error) { +func Get(c getContainer, cnr cid.ID) (*containercore.Container, error) { binCnr := make([]byte, sha256.Size) cnr.Encode(binCnr) diff --git a/pkg/morph/event/container/delete.go b/pkg/morph/event/container/delete.go index 398466f51..7286ddcfc 100644 --- a/pkg/morph/event/container/delete.go +++ b/pkg/morph/event/container/delete.go @@ -12,34 +12,34 @@ import ( // Delete structure of container.Delete notification from morph chain. type Delete struct { - containerID []byte - signature []byte - token []byte + ContainerIDValue []byte + SignatureValue []byte + TokenValue []byte // For notary notifications only. // Contains raw transactions of notary request. - notaryRequest *payload.P2PNotaryRequest + NotaryRequestValue *payload.P2PNotaryRequest } // MorphEvent implements Neo:Morph Event interface. func (Delete) MorphEvent() {} // ContainerID is a marshalled container structure, defined in API. -func (d Delete) ContainerID() []byte { return d.containerID } +func (d Delete) ContainerID() []byte { return d.ContainerIDValue } // Signature of marshalled container by container owner. -func (d Delete) Signature() []byte { return d.signature } +func (d Delete) Signature() []byte { return d.SignatureValue } // SessionToken returns binary token of the session // within which the eACL was set. func (d Delete) SessionToken() []byte { - return d.token + return d.TokenValue } // NotaryRequest returns raw notary request if notification // was received via notary service. Otherwise, returns nil. func (d Delete) NotaryRequest() *payload.P2PNotaryRequest { - return d.notaryRequest + return d.NotaryRequestValue } const expectedItemNumDelete = 3 @@ -63,19 +63,19 @@ func ParseDelete(e *state.ContainedNotificationEvent) (event.Event, error) { } // parse container - ev.containerID, err = client.BytesFromStackItem(params[0]) + ev.ContainerIDValue, err = client.BytesFromStackItem(params[0]) if err != nil { return nil, fmt.Errorf("could not get container: %w", err) } // parse signature - ev.signature, err = client.BytesFromStackItem(params[1]) + ev.SignatureValue, err = client.BytesFromStackItem(params[1]) if err != nil { return nil, fmt.Errorf("could not get signature: %w", err) } // parse session token - ev.token, err = client.BytesFromStackItem(params[2]) + ev.TokenValue, err = client.BytesFromStackItem(params[2]) if err != nil { return nil, fmt.Errorf("could not get session token: %w", err) } diff --git a/pkg/morph/event/container/delete_notary.go b/pkg/morph/event/container/delete_notary.go index 371f18733..23f13acbb 100644 --- a/pkg/morph/event/container/delete_notary.go +++ b/pkg/morph/event/container/delete_notary.go @@ -7,19 +7,19 @@ import ( func (d *Delete) setContainerID(v []byte) { if v != nil { - d.containerID = v + d.ContainerIDValue = v } } func (d *Delete) setSignature(v []byte) { if v != nil { - d.signature = v + d.SignatureValue = v } } func (d *Delete) setToken(v []byte) { if v != nil { - d.token = v + d.TokenValue = v } } @@ -62,7 +62,7 @@ func ParseDeleteNotary(ne event.NotaryEvent) (event.Event, error) { } } - ev.notaryRequest = ne.Raw() + ev.NotaryRequestValue = ne.Raw() return ev, nil } diff --git a/pkg/morph/event/container/delete_test.go b/pkg/morph/event/container/delete_test.go index 8bf894791..782f4aade 100644 --- a/pkg/morph/event/container/delete_test.go +++ b/pkg/morph/event/container/delete_test.go @@ -63,9 +63,9 @@ func TestParseDelete(t *testing.T) { require.NoError(t, err) require.Equal(t, Delete{ - containerID: containerID, - signature: signature, - token: token, + ContainerIDValue: containerID, + SignatureValue: signature, + TokenValue: token, }, ev) }) } diff --git a/pkg/morph/event/container/eacl.go b/pkg/morph/event/container/eacl.go index 8ef6a71af..41058ea43 100644 --- a/pkg/morph/event/container/eacl.go +++ b/pkg/morph/event/container/eacl.go @@ -12,14 +12,14 @@ import ( // SetEACL represents structure of notification about // modified eACL table coming from FrostFS Container contract. type SetEACL struct { - table []byte - signature []byte - publicKey []byte - token []byte + TableValue []byte + SignatureValue []byte + PublicKeyValue []byte + TokenValue []byte // For notary notifications only. // Contains raw transactions of notary request. - notaryRequest *payload.P2PNotaryRequest + NotaryRequestValue *payload.P2PNotaryRequest } // MorphEvent implements Neo:Morph Event interface. @@ -27,30 +27,30 @@ func (SetEACL) MorphEvent() {} // Table returns returns eACL table in a binary FrostFS API format. func (x SetEACL) Table() []byte { - return x.table + return x.TableValue } // Signature returns signature of the binary table. func (x SetEACL) Signature() []byte { - return x.signature + return x.SignatureValue } // PublicKey returns public keys of container // owner in a binary format. func (x SetEACL) PublicKey() []byte { - return x.publicKey + return x.PublicKeyValue } // SessionToken returns binary token of the session // within which the eACL was set. func (x SetEACL) SessionToken() []byte { - return x.token + return x.TokenValue } // NotaryRequest returns raw notary request if notification // was received via notary service. Otherwise, returns nil. func (x SetEACL) NotaryRequest() *payload.P2PNotaryRequest { - return x.notaryRequest + return x.NotaryRequestValue } const expectedItemNumEACL = 4 @@ -74,25 +74,25 @@ func ParseSetEACL(e *state.ContainedNotificationEvent) (event.Event, error) { } // parse table - ev.table, err = client.BytesFromStackItem(params[0]) + ev.TableValue, err = client.BytesFromStackItem(params[0]) if err != nil { return nil, fmt.Errorf("could not parse binary table: %w", err) } // parse signature - ev.signature, err = client.BytesFromStackItem(params[1]) + ev.SignatureValue, err = client.BytesFromStackItem(params[1]) if err != nil { return nil, fmt.Errorf("could not parse table signature: %w", err) } // parse public key - ev.publicKey, err = client.BytesFromStackItem(params[2]) + ev.PublicKeyValue, err = client.BytesFromStackItem(params[2]) if err != nil { return nil, fmt.Errorf("could not parse binary public key: %w", err) } // parse session token - ev.token, err = client.BytesFromStackItem(params[3]) + ev.TokenValue, err = client.BytesFromStackItem(params[3]) if err != nil { return nil, fmt.Errorf("could not get session token: %w", err) } diff --git a/pkg/morph/event/container/eacl_notary.go b/pkg/morph/event/container/eacl_notary.go index 112065b42..a4fe7c966 100644 --- a/pkg/morph/event/container/eacl_notary.go +++ b/pkg/morph/event/container/eacl_notary.go @@ -7,25 +7,25 @@ import ( func (x *SetEACL) setTable(v []byte) { if v != nil { - x.table = v + x.TableValue = v } } func (x *SetEACL) setSignature(v []byte) { if v != nil { - x.signature = v + x.SignatureValue = v } } func (x *SetEACL) setPublicKey(v []byte) { if v != nil { - x.publicKey = v + x.PublicKeyValue = v } } func (x *SetEACL) setToken(v []byte) { if v != nil { - x.token = v + x.TokenValue = v } } @@ -69,7 +69,7 @@ func ParseSetEACLNotary(ne event.NotaryEvent) (event.Event, error) { } } - ev.notaryRequest = ne.Raw() + ev.NotaryRequestValue = ne.Raw() return ev, nil } diff --git a/pkg/morph/event/frostfs/bind.go b/pkg/morph/event/frostfs/bind.go index 49d10d3c3..8655b1222 100644 --- a/pkg/morph/event/frostfs/bind.go +++ b/pkg/morph/event/frostfs/bind.go @@ -11,31 +11,31 @@ import ( ) type Bind struct { - bindCommon + BindCommon } -type bindCommon struct { - user []byte - keys [][]byte +type BindCommon struct { + UserValue []byte + KeysValue [][]byte - // txHash is used in notary environmental + // TxHashValue is used in notary environmental // for calculating unique but same for // all notification receivers values. - txHash util.Uint256 + TxHashValue util.Uint256 } // TxHash returns hash of the TX with new epoch // notification. -func (b bindCommon) TxHash() util.Uint256 { - return b.txHash +func (b BindCommon) TxHash() util.Uint256 { + return b.TxHashValue } // MorphEvent implements Neo:Morph Event interface. -func (bindCommon) MorphEvent() {} +func (BindCommon) MorphEvent() {} -func (b bindCommon) Keys() [][]byte { return b.keys } +func (b BindCommon) Keys() [][]byte { return b.KeysValue } -func (b bindCommon) User() []byte { return b.user } +func (b BindCommon) User() []byte { return b.UserValue } func ParseBind(e *state.ContainedNotificationEvent) (event.Event, error) { var ( @@ -48,17 +48,17 @@ func ParseBind(e *state.ContainedNotificationEvent) (event.Event, error) { return nil, fmt.Errorf("could not parse stack items from notify event: %w", err) } - err = parseBind(&ev.bindCommon, params) + err = parseBind(&ev.BindCommon, params) if err != nil { return nil, err } - ev.txHash = e.Container + ev.TxHashValue = e.Container return ev, nil } -func parseBind(dst *bindCommon, params []stackitem.Item) error { +func parseBind(dst *BindCommon, params []stackitem.Item) error { if ln := len(params); ln != 2 { return event.WrongNumberOfParameters(2, ln) } @@ -66,7 +66,7 @@ func parseBind(dst *bindCommon, params []stackitem.Item) error { var err error // parse user - dst.user, err = client.BytesFromStackItem(params[0]) + dst.UserValue, err = client.BytesFromStackItem(params[0]) if err != nil { return fmt.Errorf("could not get bind user: %w", err) } @@ -77,7 +77,7 @@ func parseBind(dst *bindCommon, params []stackitem.Item) error { return fmt.Errorf("could not get bind keys: %w", err) } - dst.keys = make([][]byte, 0, len(bindKeys)) + dst.KeysValue = make([][]byte, 0, len(bindKeys)) for i := range bindKeys { rawKey, err := client.BytesFromStackItem(bindKeys[i]) @@ -85,7 +85,7 @@ func parseBind(dst *bindCommon, params []stackitem.Item) error { return fmt.Errorf("could not get bind public key: %w", err) } - dst.keys = append(dst.keys, rawKey) + dst.KeysValue = append(dst.KeysValue, rawKey) } return nil diff --git a/pkg/morph/event/frostfs/cheque.go b/pkg/morph/event/frostfs/cheque.go index 239ddb1a4..eae2a23f5 100644 --- a/pkg/morph/event/frostfs/cheque.go +++ b/pkg/morph/event/frostfs/cheque.go @@ -11,26 +11,26 @@ import ( // Cheque structure of frostfs.Cheque notification from mainnet chain. type Cheque struct { - id []byte - amount int64 // Fixed8 - user util.Uint160 - lock util.Uint160 + IDValue []byte + AmountValue int64 // Fixed8 + UserValue util.Uint160 + LockValue util.Uint160 } // MorphEvent implements Neo:Morph Event interface. func (Cheque) MorphEvent() {} // ID is a withdraw transaction hash. -func (c Cheque) ID() []byte { return c.id } +func (c Cheque) ID() []byte { return c.IDValue } // User returns withdraw receiver script hash from main net. -func (c Cheque) User() util.Uint160 { return c.user } +func (c Cheque) User() util.Uint160 { return c.UserValue } // Amount of the sent assets. -func (c Cheque) Amount() int64 { return c.amount } +func (c Cheque) Amount() int64 { return c.AmountValue } // LockAccount return script hash for balance contract wallet. -func (c Cheque) LockAccount() util.Uint160 { return c.lock } +func (c Cheque) LockAccount() util.Uint160 { return c.LockValue } // ParseCheque from notification into cheque structure. func ParseCheque(e *state.ContainedNotificationEvent) (event.Event, error) { @@ -49,7 +49,7 @@ func ParseCheque(e *state.ContainedNotificationEvent) (event.Event, error) { } // parse id - ev.id, err = client.BytesFromStackItem(params[0]) + ev.IDValue, err = client.BytesFromStackItem(params[0]) if err != nil { return nil, fmt.Errorf("could not get cheque id: %w", err) } @@ -60,13 +60,13 @@ func ParseCheque(e *state.ContainedNotificationEvent) (event.Event, error) { return nil, fmt.Errorf("could not get cheque user: %w", err) } - ev.user, err = util.Uint160DecodeBytesBE(user) + ev.UserValue, err = util.Uint160DecodeBytesBE(user) if err != nil { return nil, fmt.Errorf("could not convert cheque user to uint160: %w", err) } // parse amount - ev.amount, err = client.IntFromStackItem(params[2]) + ev.AmountValue, err = client.IntFromStackItem(params[2]) if err != nil { return nil, fmt.Errorf("could not get cheque amount: %w", err) } @@ -77,7 +77,7 @@ func ParseCheque(e *state.ContainedNotificationEvent) (event.Event, error) { return nil, fmt.Errorf("could not get cheque lock account: %w", err) } - ev.lock, err = util.Uint160DecodeBytesBE(lock) + ev.LockValue, err = util.Uint160DecodeBytesBE(lock) if err != nil { return nil, fmt.Errorf("could not convert cheque lock account to uint160: %w", err) } diff --git a/pkg/morph/event/frostfs/cheque_test.go b/pkg/morph/event/frostfs/cheque_test.go index 861f05a68..be53592ca 100644 --- a/pkg/morph/event/frostfs/cheque_test.go +++ b/pkg/morph/event/frostfs/cheque_test.go @@ -77,10 +77,10 @@ func TestParseCheque(t *testing.T) { require.NoError(t, err) require.Equal(t, Cheque{ - id: id, - amount: amount, - user: user, - lock: lock, + IDValue: id, + AmountValue: amount, + UserValue: user, + LockValue: lock, }, ev) }) } diff --git a/pkg/morph/event/frostfs/config.go b/pkg/morph/event/frostfs/config.go index 1b9824b39..4c87634c2 100644 --- a/pkg/morph/event/frostfs/config.go +++ b/pkg/morph/event/frostfs/config.go @@ -10,30 +10,30 @@ import ( ) type Config struct { - key []byte - value []byte - id []byte + KeyValue []byte + ValueValue []byte + IDValue []byte - // txHash is used in notary environmental + // TxHashValue is used in notary environmental // for calculating unique but same for // all notification receivers values. - txHash util.Uint256 + TxHashValue util.Uint256 } // TxHash returns hash of the TX with new epoch // notification. func (u Config) TxHash() util.Uint256 { - return u.txHash + return u.TxHashValue } // MorphEvent implements Neo:Morph Event interface. func (Config) MorphEvent() {} -func (u Config) ID() []byte { return u.id } +func (u Config) ID() []byte { return u.IDValue } -func (u Config) Key() []byte { return u.key } +func (u Config) Key() []byte { return u.KeyValue } -func (u Config) Value() []byte { return u.value } +func (u Config) Value() []byte { return u.ValueValue } func ParseConfig(e *state.ContainedNotificationEvent) (event.Event, error) { var ( @@ -51,24 +51,24 @@ func ParseConfig(e *state.ContainedNotificationEvent) (event.Event, error) { } // parse id - ev.id, err = client.BytesFromStackItem(params[0]) + ev.IDValue, err = client.BytesFromStackItem(params[0]) if err != nil { return nil, fmt.Errorf("could not get config update id: %w", err) } // parse key - ev.key, err = client.BytesFromStackItem(params[1]) + ev.KeyValue, err = client.BytesFromStackItem(params[1]) if err != nil { return nil, fmt.Errorf("could not get config key: %w", err) } // parse value - ev.value, err = client.BytesFromStackItem(params[2]) + ev.ValueValue, err = client.BytesFromStackItem(params[2]) if err != nil { return nil, fmt.Errorf("could not get config value: %w", err) } - ev.txHash = e.Container + ev.TxHashValue = e.Container return ev, nil } diff --git a/pkg/morph/event/frostfs/config_test.go b/pkg/morph/event/frostfs/config_test.go index b56c8ecb2..dcd4201e4 100644 --- a/pkg/morph/event/frostfs/config_test.go +++ b/pkg/morph/event/frostfs/config_test.go @@ -60,9 +60,9 @@ func TestParseConfig(t *testing.T) { require.NoError(t, err) require.Equal(t, Config{ - id: id, - key: key, - value: value, + IDValue: id, + KeyValue: key, + ValueValue: value, }, ev) }) } diff --git a/pkg/morph/event/frostfs/deposit.go b/pkg/morph/event/frostfs/deposit.go index b9467d112..d8a3b82f0 100644 --- a/pkg/morph/event/frostfs/deposit.go +++ b/pkg/morph/event/frostfs/deposit.go @@ -11,26 +11,26 @@ import ( // Deposit structure of frostfs.Deposit notification from mainnet chain. type Deposit struct { - id []byte - amount int64 // Fixed8 - from util.Uint160 - to util.Uint160 + IDValue []byte + AmountValue int64 // Fixed8 + FromValue util.Uint160 + ToValue util.Uint160 } // MorphEvent implements Neo:Morph Event interface. func (Deposit) MorphEvent() {} // ID is a deposit transaction hash. -func (d Deposit) ID() []byte { return d.id } +func (d Deposit) ID() []byte { return d.IDValue } // From is a script hash of asset sender in main net. -func (d Deposit) From() util.Uint160 { return d.from } +func (d Deposit) From() util.Uint160 { return d.FromValue } // To is a script hash of asset receiver in balance contract. -func (d Deposit) To() util.Uint160 { return d.to } +func (d Deposit) To() util.Uint160 { return d.ToValue } // Amount of transferred assets. -func (d Deposit) Amount() int64 { return d.amount } +func (d Deposit) Amount() int64 { return d.AmountValue } // ParseDeposit notification into deposit structure. func ParseDeposit(e *state.ContainedNotificationEvent) (event.Event, error) { @@ -51,13 +51,13 @@ func ParseDeposit(e *state.ContainedNotificationEvent) (event.Event, error) { return nil, fmt.Errorf("could not get deposit sender: %w", err) } - ev.from, err = util.Uint160DecodeBytesBE(from) + ev.FromValue, err = util.Uint160DecodeBytesBE(from) if err != nil { return nil, fmt.Errorf("could not convert deposit sender to uint160: %w", err) } // parse amount - ev.amount, err = client.IntFromStackItem(params[1]) + ev.AmountValue, err = client.IntFromStackItem(params[1]) if err != nil { return nil, fmt.Errorf("could not get deposit amount: %w", err) } @@ -68,13 +68,13 @@ func ParseDeposit(e *state.ContainedNotificationEvent) (event.Event, error) { return nil, fmt.Errorf("could not get deposit receiver: %w", err) } - ev.to, err = util.Uint160DecodeBytesBE(to) + ev.ToValue, err = util.Uint160DecodeBytesBE(to) if err != nil { return nil, fmt.Errorf("could not convert deposit receiver to uint160: %w", err) } // parse id - ev.id, err = client.BytesFromStackItem(params[3]) + ev.IDValue, err = client.BytesFromStackItem(params[3]) if err != nil { return nil, fmt.Errorf("could not get deposit id: %w", err) } diff --git a/pkg/morph/event/frostfs/deposit_test.go b/pkg/morph/event/frostfs/deposit_test.go index 0f52e2119..f279a7f9c 100644 --- a/pkg/morph/event/frostfs/deposit_test.go +++ b/pkg/morph/event/frostfs/deposit_test.go @@ -77,10 +77,10 @@ func TestParseDeposit(t *testing.T) { require.NoError(t, err) require.Equal(t, Deposit{ - id: id, - amount: amount, - from: from, - to: to, + IDValue: id, + AmountValue: amount, + FromValue: from, + ToValue: to, }, ev) }) } diff --git a/pkg/morph/event/frostfs/unbind.go b/pkg/morph/event/frostfs/unbind.go index f88d67994..5a6a8dad9 100644 --- a/pkg/morph/event/frostfs/unbind.go +++ b/pkg/morph/event/frostfs/unbind.go @@ -8,7 +8,7 @@ import ( ) type Unbind struct { - bindCommon + BindCommon } func ParseUnbind(e *state.ContainedNotificationEvent) (event.Event, error) { @@ -22,12 +22,12 @@ func ParseUnbind(e *state.ContainedNotificationEvent) (event.Event, error) { return nil, fmt.Errorf("could not parse stack items from notify event: %w", err) } - err = parseBind(&ev.bindCommon, params) + err = parseBind(&ev.BindCommon, params) if err != nil { return nil, err } - ev.txHash = e.Container + ev.TxHashValue = e.Container return ev, nil } diff --git a/pkg/morph/event/frostfs/withdraw.go b/pkg/morph/event/frostfs/withdraw.go index 3bbf76c2c..f48067f86 100644 --- a/pkg/morph/event/frostfs/withdraw.go +++ b/pkg/morph/event/frostfs/withdraw.go @@ -11,22 +11,22 @@ import ( // Withdraw structure of frostfs.Withdraw notification from mainnet chain. type Withdraw struct { - id []byte - amount int64 // Fixed8 - user util.Uint160 + IDValue []byte + AmountValue int64 // Fixed8 + UserValue util.Uint160 } // MorphEvent implements Neo:Morph Event interface. func (Withdraw) MorphEvent() {} // ID is a withdraw transaction hash. -func (w Withdraw) ID() []byte { return w.id } +func (w Withdraw) ID() []byte { return w.IDValue } // User returns withdraw receiver script hash from main net. -func (w Withdraw) User() util.Uint160 { return w.user } +func (w Withdraw) User() util.Uint160 { return w.UserValue } // Amount of the withdraw assets. -func (w Withdraw) Amount() int64 { return w.amount } +func (w Withdraw) Amount() int64 { return w.AmountValue } // ParseWithdraw notification into withdraw structure. func ParseWithdraw(e *state.ContainedNotificationEvent) (event.Event, error) { @@ -47,19 +47,19 @@ func ParseWithdraw(e *state.ContainedNotificationEvent) (event.Event, error) { return nil, fmt.Errorf("could not get withdraw user: %w", err) } - ev.user, err = util.Uint160DecodeBytesBE(user) + ev.UserValue, err = util.Uint160DecodeBytesBE(user) if err != nil { return nil, fmt.Errorf("could not convert withdraw user to uint160: %w", err) } // parse amount - ev.amount, err = client.IntFromStackItem(params[1]) + ev.AmountValue, err = client.IntFromStackItem(params[1]) if err != nil { return nil, fmt.Errorf("could not get withdraw amount: %w", err) } // parse id - ev.id, err = client.BytesFromStackItem(params[2]) + ev.IDValue, err = client.BytesFromStackItem(params[2]) if err != nil { return nil, fmt.Errorf("could not get withdraw id: %w", err) } diff --git a/pkg/morph/event/frostfs/withdraw_test.go b/pkg/morph/event/frostfs/withdraw_test.go index 5544283ef..33435d19a 100644 --- a/pkg/morph/event/frostfs/withdraw_test.go +++ b/pkg/morph/event/frostfs/withdraw_test.go @@ -64,9 +64,9 @@ func TestParseWithdraw(t *testing.T) { require.NoError(t, err) require.Equal(t, Withdraw{ - id: id, - amount: amount, - user: user, + IDValue: id, + AmountValue: amount, + UserValue: user, }, ev) }) } diff --git a/pkg/morph/event/netmap/add_peer.go b/pkg/morph/event/netmap/add_peer.go index 87cf94082..6f839bada 100644 --- a/pkg/morph/event/netmap/add_peer.go +++ b/pkg/morph/event/netmap/add_peer.go @@ -10,24 +10,24 @@ import ( ) type AddPeer struct { - node []byte + NodeBytes []byte // For notary notifications only. // Contains raw transactions of notary request. - notaryRequest *payload.P2PNotaryRequest + Request *payload.P2PNotaryRequest } // MorphEvent implements Neo:Morph Event interface. func (AddPeer) MorphEvent() {} func (s AddPeer) Node() []byte { - return s.node + return s.NodeBytes } // NotaryRequest returns raw notary request if notification // was received via notary service. Otherwise, returns nil. func (s AddPeer) NotaryRequest() *payload.P2PNotaryRequest { - return s.notaryRequest + return s.Request } const expectedItemNumAddPeer = 1 @@ -47,7 +47,7 @@ func ParseAddPeer(e *state.ContainedNotificationEvent) (event.Event, error) { return nil, event.WrongNumberOfParameters(expectedItemNumAddPeer, ln) } - ev.node, err = client.BytesFromStackItem(params[0]) + ev.NodeBytes, err = client.BytesFromStackItem(params[0]) if err != nil { return nil, fmt.Errorf("could not get raw nodeinfo: %w", err) } diff --git a/pkg/morph/event/netmap/add_peer_notary.go b/pkg/morph/event/netmap/add_peer_notary.go index a506b052d..a24722a97 100644 --- a/pkg/morph/event/netmap/add_peer_notary.go +++ b/pkg/morph/event/netmap/add_peer_notary.go @@ -7,7 +7,7 @@ import ( func (s *AddPeer) setNode(v []byte) { if v != nil { - s.node = v + s.NodeBytes = v } } @@ -43,7 +43,7 @@ func ParseAddPeerNotary(ne event.NotaryEvent) (event.Event, error) { } } - ev.notaryRequest = ne.Raw() + ev.Request = ne.Raw() return ev, nil } diff --git a/pkg/morph/event/netmap/add_peer_test.go b/pkg/morph/event/netmap/add_peer_test.go index 1b8bcf40a..0574c4048 100644 --- a/pkg/morph/event/netmap/add_peer_test.go +++ b/pkg/morph/event/netmap/add_peer_test.go @@ -37,7 +37,7 @@ func TestParseAddPeer(t *testing.T) { require.NoError(t, err) require.Equal(t, AddPeer{ - node: info, + NodeBytes: info, }, ev) }) } diff --git a/pkg/morph/event/netmap/epoch.go b/pkg/morph/event/netmap/epoch.go index 0eaa9f285..e454e2a6a 100644 --- a/pkg/morph/event/netmap/epoch.go +++ b/pkg/morph/event/netmap/epoch.go @@ -11,12 +11,12 @@ import ( // NewEpoch is a new epoch Neo:Morph event. type NewEpoch struct { - num uint64 + Num uint64 - // txHash is used in notary environmental + // Hash is used in notary environmental // for calculating unique but same for // all notification receivers values. - txHash util.Uint256 + Hash util.Uint256 } // MorphEvent implements Neo:Morph Event interface. @@ -24,13 +24,13 @@ func (NewEpoch) MorphEvent() {} // EpochNumber returns new epoch number. func (s NewEpoch) EpochNumber() uint64 { - return s.num + return s.Num } // TxHash returns hash of the TX with new epoch // notification. func (s NewEpoch) TxHash() util.Uint256 { - return s.txHash + return s.Hash } // ParseNewEpoch is a parser of new epoch notification event. @@ -52,7 +52,7 @@ func ParseNewEpoch(e *state.ContainedNotificationEvent) (event.Event, error) { } return NewEpoch{ - num: uint64(prmEpochNum), - txHash: e.Container, + Num: uint64(prmEpochNum), + Hash: e.Container, }, nil } diff --git a/pkg/morph/event/netmap/epoch_test.go b/pkg/morph/event/netmap/epoch_test.go index b175b5275..bc267ecb6 100644 --- a/pkg/morph/event/netmap/epoch_test.go +++ b/pkg/morph/event/netmap/epoch_test.go @@ -37,7 +37,7 @@ func TestParseNewEpoch(t *testing.T) { require.NoError(t, err) require.Equal(t, NewEpoch{ - num: epochNum, + Num: epochNum, }, ev) }) } diff --git a/pkg/morph/event/netmap/update_peer.go b/pkg/morph/event/netmap/update_peer.go index 535d57e4d..f02ca408d 100644 --- a/pkg/morph/event/netmap/update_peer.go +++ b/pkg/morph/event/netmap/update_peer.go @@ -13,13 +13,13 @@ import ( ) type UpdatePeer struct { - publicKey *keys.PublicKey + PubKey *keys.PublicKey - state netmap.NodeState + State netmap.NodeState // For notary notifications only. // Contains raw transactions of notary request. - notaryRequest *payload.P2PNotaryRequest + Request *payload.P2PNotaryRequest } // MorphEvent implements Neo:Morph Event interface. @@ -28,27 +28,27 @@ func (UpdatePeer) MorphEvent() {} // Online returns true if node's state is requested to be switched // to "online". func (s UpdatePeer) Online() bool { - return s.state == netmap.NodeStateOnline + return s.State == netmap.NodeStateOnline } // Maintenance returns true if node's state is requested to be switched // to "maintenance". func (s UpdatePeer) Maintenance() bool { - return s.state == netmap.NodeStateMaintenance + return s.State == netmap.NodeStateMaintenance } func (s UpdatePeer) PublicKey() *keys.PublicKey { - return s.publicKey + return s.PubKey } // NotaryRequest returns raw notary request if notification // was received via notary service. Otherwise, returns nil. func (s UpdatePeer) NotaryRequest() *payload.P2PNotaryRequest { - return s.notaryRequest + return s.Request } func (s *UpdatePeer) decodeState(state int64) error { - switch s.state = netmap.NodeState(state); s.state { + switch s.State = netmap.NodeState(state); s.State { default: return fmt.Errorf("unsupported node state %d", state) case @@ -82,7 +82,7 @@ func ParseUpdatePeer(e *state.ContainedNotificationEvent) (event.Event, error) { return nil, fmt.Errorf("could not get public key: %w", err) } - ev.publicKey, err = keys.NewPublicKeyFromBytes(key, elliptic.P256()) + ev.PubKey, err = keys.NewPublicKeyFromBytes(key, elliptic.P256()) if err != nil { return nil, fmt.Errorf("could not parse public key: %w", err) } diff --git a/pkg/morph/event/netmap/update_peer_notary.go b/pkg/morph/event/netmap/update_peer_notary.go index b7a251f98..0260810b8 100644 --- a/pkg/morph/event/netmap/update_peer_notary.go +++ b/pkg/morph/event/netmap/update_peer_notary.go @@ -17,7 +17,7 @@ func (s *UpdatePeer) setPublicKey(v []byte) (err error) { return errNilPubKey } - s.publicKey, err = keys.NewPublicKeyFromBytes(v, elliptic.P256()) + s.PubKey, err = keys.NewPublicKeyFromBytes(v, elliptic.P256()) if err != nil { return fmt.Errorf("could not parse public key: %w", err) } @@ -73,7 +73,7 @@ func ParseUpdatePeerNotary(ne event.NotaryEvent) (event.Event, error) { } } - ev.notaryRequest = ne.Raw() + ev.Request = ne.Raw() return ev, nil } diff --git a/pkg/morph/event/netmap/update_peer_test.go b/pkg/morph/event/netmap/update_peer_test.go index 1772c88a7..b79dd6385 100644 --- a/pkg/morph/event/netmap/update_peer_test.go +++ b/pkg/morph/event/netmap/update_peer_test.go @@ -52,8 +52,8 @@ func TestParseUpdatePeer(t *testing.T) { require.NoError(t, err) require.Equal(t, UpdatePeer{ - publicKey: publicKey, - state: state, + PubKey: publicKey, + State: state, }, ev) }) }