Skip to content

Commit 03db265

Browse files
committed
ir/container: Implement iterative background structuring in contract
Within nspcc-dev/neofs-contract#449, there is a need to store containers as VM structs. Since nspcc-dev/neofs-contract#534, struct items are stored for new containers. This also needs to be done for containers created before the upgrade. Since doing this in a contract update transaction turned out to be too GAS-intensive, this implements structuring through the IR background process. Specialized method is called iteratively doing the same thing, but for several containers at a time. Signed-off-by: Leonard Lyubich <[email protected]>
1 parent 2e73605 commit 03db265

File tree

6 files changed

+122
-11
lines changed

6 files changed

+122
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Changelog for NeoFS Node
1010
- `neofs-adm fschain load-report` command (#3649)
1111
- SN now supports new `getInfo` and `createV2` methods of the Container contract (#3670)
1212
- IR now supports container creation requests submitted via new `createV2` contract method (#3670)
13+
- IR structures containers in the contract iteratively (#3670)
1314

1415
### Fixed
1516
- Write cache using too much CPU (#3642)

pkg/innerring/innerring.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ type (
8888
withoutMainNet bool
8989

9090
// runtime processors
91-
netmapProcessor *netmap.Processor
91+
netmapProcessor *netmap.Processor
92+
containerProcessor *container.Processor
9293

9394
workers []func(context.Context)
9495

@@ -229,6 +230,12 @@ func (s *Server) Start(ctx context.Context, intError chan<- error) (err error) {
229230
go s.fsChainListener.ListenWithError(ctx, fsChainErr) // listen for neo:fs events
230231
go s.mainnetListener.ListenWithError(ctx, mainnnetErr) // listen for neo:mainnet events
231232

233+
go func() {
234+
if err := s.containerProcessor.AddContainerStructs(ctx); err != nil {
235+
fsChainErr <- fmt.Errorf("structurize containers in the contract: %w", err)
236+
}
237+
}()
238+
232239
s.startWorkers(ctx)
233240

234241
return nil
@@ -820,7 +827,7 @@ func New(ctx context.Context, log *zap.Logger, cfg *config.Config, errChan chan<
820827
}
821828

822829
// container processor
823-
containerProcessor, err := container.New(&container.Params{
830+
server.containerProcessor, err = container.New(&container.Params{
824831
Log: log,
825832
PoolSize: cfg.Workers.Container,
826833
AlphabetState: server,
@@ -833,7 +840,7 @@ func New(ctx context.Context, log *zap.Logger, cfg *config.Config, errChan chan<
833840
return nil, err
834841
}
835842

836-
err = bindFSChainProcessor(containerProcessor, server)
843+
err = bindFSChainProcessor(server.containerProcessor, server)
837844
if err != nil {
838845
return nil, err
839846
}

pkg/innerring/processors/container/processor.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package container
22

33
import (
4+
"context"
45
"errors"
56
"fmt"
7+
"time"
68

9+
"github.com/nspcc-dev/neo-go/pkg/neorpc"
10+
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
711
"github.com/nspcc-dev/neofs-node/pkg/morph/client/container"
812
fschaincontracts "github.com/nspcc-dev/neofs-node/pkg/morph/contracts"
913
"github.com/nspcc-dev/neofs-node/pkg/morph/event"
@@ -171,6 +175,10 @@ func (cp *Processor) ListenerNotaryParsers() []event.NotaryParserInfo {
171175
p.SetParser(containerEvent.ParseObjectPut)
172176
pp = append(pp, p)
173177

178+
// migrate protobuf->struct
179+
p.SetRequestType(fschaincontracts.AddContainerStructsMethod)
180+
p.SetParser(containerEvent.RestoreAddStructsRequest)
181+
174182
return pp
175183
}
176184

@@ -232,10 +240,69 @@ func (cp *Processor) ListenerNotaryHandlers() []event.NotaryHandlerInfo {
232240
h.SetHandler(cp.handleObjectPut)
233241
hh = append(hh, h)
234242

243+
// migrate protobuf->struct
244+
h.SetRequestType(fschaincontracts.AddContainerStructsMethod)
245+
h.SetHandler(func(ev event.Event) {
246+
cp.log.Info("received notary tx migrating containers' protobuf->struct, signing...")
247+
248+
req := ev.(containerEvent.AddStructsRequest)
249+
err := cp.cnrClient.Morph().NotarySignAndInvokeTX(&req.MainTransaction, false)
250+
if err != nil {
251+
cp.log.Error("failed to sign notary tx migrating containers' protobuf->struct", zap.Error(err))
252+
return
253+
}
254+
255+
cp.log.Info("notary tx migrating containers' protobuf->struct signed successfully")
256+
})
257+
235258
return hh
236259
}
237260

238261
// TimersHandlers for the 'Timers' event producer.
239262
func (cp *Processor) TimersHandlers() []event.NotificationHandlerInfo {
240263
return nil
241264
}
265+
266+
// AddContainerStructs iteratively calls the contract to add structured storage
267+
// items for containers.
268+
func (cp *Processor) AddContainerStructs(ctx context.Context) error {
269+
cp.log.Info("structuring containers in the contract...")
270+
271+
cnrContract := cp.cnrClient.ContractAddress()
272+
fsChain := cp.cnrClient.Morph()
273+
for ; ; time.Sleep(5 * time.Second) {
274+
txRes, err := fsChain.CallNotary(ctx, cnrContract, fschaincontracts.AddContainerStructsMethod)
275+
if err != nil {
276+
if !errors.Is(err, neorpc.ErrInsufficientFunds) {
277+
return fmt.Errorf("notary call %s contract method: %w", fschaincontracts.AddContainerStructsMethod, err)
278+
}
279+
280+
cp.log.Warn("not enough GAS for notary call, will try again later",
281+
zap.String("method", fschaincontracts.AddContainerStructsMethod), zap.Error(err))
282+
continue
283+
}
284+
285+
if txRes.VMState.HasFlag(vmstate.Halt) {
286+
cp.log.Warn("non-HALT VM state, will try again later",
287+
zap.String("method", fschaincontracts.AddContainerStructsMethod), zap.Stringer("state", txRes.VMState),
288+
zap.String("expection", txRes.FaultException), zap.String("tx", txRes.Container.StringLE()))
289+
continue
290+
}
291+
292+
if len(txRes.Stack) == 0 {
293+
return fmt.Errorf("empty stack in %s call result, tx %s", fschaincontracts.AddContainerStructsMethod, txRes.Container)
294+
}
295+
296+
b, err := txRes.Stack[0].TryBool()
297+
if err != nil {
298+
return fmt.Errorf("convert stack item in %s call result to bool (tx %s); %w", fschaincontracts.AddContainerStructsMethod, txRes.Container, err)
299+
}
300+
301+
if !b {
302+
cp.log.Warn("all containers have benn successfully structured in the contract, interrupt")
303+
return nil
304+
}
305+
306+
cp.log.Info("more containers have been successfully structured in the contract, continue")
307+
}
308+
}

pkg/morph/client/notary.go

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/cenkalti/backoff/v4"
1515
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
16+
"github.com/nspcc-dev/neo-go/pkg/core/state"
1617
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
1718
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
1819
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@@ -323,6 +324,13 @@ func (c *Client) NotaryInvoke(ctx context.Context, contract util.Uint160, await
323324
return c.notaryInvoke(false, true, contract, await, nonce, vub, method, args...)
324325
}
325326

327+
// CallNotary calls contract method requiring Alphabet witness using Notary
328+
// service and returns transaction result.
329+
func (c *Client) CallNotary(_ context.Context, contract util.Uint160, method string, args ...any) (*state.AppExecResult, error) {
330+
_, res, err := c._notaryInvoke(false, true, contract, true, 0, nil, method, args...)
331+
return res, err
332+
}
333+
326334
// NotaryInvokeNotAlpha does the same as NotaryInvoke but does not use client's
327335
// private key in Invocation script. It means that main TX of notary request is
328336
// not expected to be signed by the current node.
@@ -475,15 +483,20 @@ func (c *Client) notaryInvokeAsCommittee(method string, nonce, vub uint32, args
475483
}
476484

477485
func (c *Client) notaryInvoke(committee, invokedByAlpha bool, contract util.Uint160, await bool, nonce uint32, vub *uint32, method string, args ...any) (util.Uint256, error) {
486+
txHash, _, err := c._notaryInvoke(committee, invokedByAlpha, contract, await, nonce, vub, method, args)
487+
return txHash, err
488+
}
489+
490+
func (c *Client) _notaryInvoke(committee, invokedByAlpha bool, contract util.Uint160, await bool, nonce uint32, vub *uint32, method string, args ...any) (util.Uint256, *state.AppExecResult, error) {
478491
var conn = c.conn.Load()
479492

480493
if conn == nil {
481-
return util.Uint256{}, ErrConnectionLost
494+
return util.Uint256{}, nil, ErrConnectionLost
482495
}
483496

484497
alphabetList, err := c.notary.alphabetSource() // prepare arguments for test invocation
485498
if err != nil {
486-
return util.Uint256{}, err
499+
return util.Uint256{}, nil, err
487500
}
488501

489502
var until uint32
@@ -493,18 +506,18 @@ func (c *Client) notaryInvoke(committee, invokedByAlpha bool, contract util.Uint
493506
} else {
494507
until, err = c.notaryTxValidationLimit(conn)
495508
if err != nil {
496-
return util.Uint256{}, err
509+
return util.Uint256{}, nil, err
497510
}
498511
}
499512

500513
cosigners, err := c.notaryCosigners(invokedByAlpha, alphabetList, committee)
501514
if err != nil {
502-
return util.Uint256{}, err
515+
return util.Uint256{}, nil, err
503516
}
504517

505518
nAct, err := notary.NewActor(conn.client, cosigners, c.acc)
506519
if err != nil {
507-
return util.Uint256{}, err
520+
return util.Uint256{}, nil, err
508521
}
509522

510523
mainH, fbH, untilActual, err := nAct.Notarize(nAct.MakeTunedCall(contract, method, nil, func(r *result.Invoke, t *transaction.Transaction) error {
@@ -521,12 +534,14 @@ func (c *Client) notaryInvoke(committee, invokedByAlpha bool, contract util.Uint
521534

522535
return nil
523536
}, args...))
537+
538+
var res *state.AppExecResult
524539
if await {
525-
_, err = nAct.WaitSuccess(mainH, fbH, untilActual, err)
540+
res, err = nAct.WaitSuccess(mainH, fbH, untilActual, err)
526541
}
527542

528543
if err != nil && !alreadyOnChainError(err) {
529-
return util.Uint256{}, err
544+
return util.Uint256{}, nil, err
530545
}
531546

532547
c.logger.Debug("notary request invoked",
@@ -535,7 +550,7 @@ func (c *Client) notaryInvoke(committee, invokedByAlpha bool, contract util.Uint
535550
zap.String("tx_hash", mainH.StringLE()),
536551
zap.String("fallback_hash", fbH.StringLE()))
537552

538-
return mainH, nil
553+
return mainH, res, nil
539554
}
540555

541556
func (c *Client) runAlphabetNotaryScript(script []byte, nonce uint32) error {

pkg/morph/contracts/methods.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const (
1212
GetTakenSpaceByUserMethod = "getTakenSpaceByUser"
1313
GetContainerQuotaMethod = "containerQuota"
1414
GetUserQuotaMethod = "userQuota"
15+
AddContainerStructsMethod = "addStructs"
1516
)
1617

1718
// CreateContainerParams are parameters of [CreateContainerMethod].

pkg/morph/event/container/notary_requests.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,23 @@ func RestorePutContainerEACLRequest(notaryReq event.NotaryEvent) (event.Event, e
195195

196196
return res, nil
197197
}
198+
199+
// AddStructsRequest wraps container protobuf->struct migration request to
200+
// provide app-internal event.
201+
type AddStructsRequest struct {
202+
event.Event
203+
MainTransaction transaction.Transaction
204+
}
205+
206+
// RestoreAddStructsRequest restores [AddStructsRequest] from the
207+
// notary one.
208+
func RestoreAddStructsRequest(notaryReq event.NotaryEvent) (event.Event, error) {
209+
_, err := getArgsFromEvent(notaryReq, 0)
210+
if err != nil {
211+
return nil, err
212+
}
213+
214+
return AddStructsRequest{
215+
MainTransaction: *notaryReq.Raw().MainTransaction,
216+
}, nil
217+
}

0 commit comments

Comments
 (0)