Skip to content

Commit f6c53ef

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 <leonard@morphbits.io>
1 parent 5558730 commit f6c53ef

File tree

6 files changed

+125
-12
lines changed

6 files changed

+125
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Changelog for NeoFS Node
66
### Added
77
- SN now supports new `getInfo` and `createV2` methods of the Container contract (#3670)
88
- IR now supports container creation requests submitted via new `createV2` contract method (#3670)
9+
- IR structures containers in the contract iteratively (#3670)
910

1011
### Fixed
1112

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: 69 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,71 @@ 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+
txs := txRes.Container.StringLE()
286+
287+
if !txRes.VMState.HasFlag(vmstate.Halt) {
288+
cp.log.Warn("non-HALT VM state, will try again later",
289+
zap.String("method", fschaincontracts.AddContainerStructsMethod), zap.Stringer("state", txRes.VMState),
290+
zap.String("exception", txRes.FaultException), zap.String("tx", txs))
291+
continue
292+
}
293+
294+
if len(txRes.Stack) == 0 {
295+
return fmt.Errorf("empty stack in %s call result, tx %s", fschaincontracts.AddContainerStructsMethod, txs)
296+
}
297+
298+
b, err := txRes.Stack[0].TryBool()
299+
if err != nil {
300+
return fmt.Errorf("convert stack item in %s call result to bool (tx %s); %w", fschaincontracts.AddContainerStructsMethod, txs, err)
301+
}
302+
303+
if !b {
304+
cp.log.Warn("all containers have been successfully structured in the contract, interrupt", zap.String("tx", txs))
305+
return nil
306+
}
307+
308+
cp.log.Info("more containers have been successfully structured in the contract, continue", zap.String("tx", txs))
309+
}
310+
}

pkg/morph/client/notary.go

Lines changed: 24 additions & 9 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.
@@ -484,15 +492,20 @@ func (c *Client) notaryInvokeAsCommittee(method string, nonce, vub uint32, args
484492
}
485493

486494
func (c *Client) notaryInvoke(committee, invokedByAlpha bool, contract util.Uint160, await bool, nonce uint32, vub *uint32, method string, args ...any) (util.Uint256, error) {
495+
txHash, _, err := c._notaryInvoke(committee, invokedByAlpha, contract, await, nonce, vub, method, args)
496+
return txHash, err
497+
}
498+
499+
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) {
487500
var conn = c.conn.Load()
488501

489502
if conn == nil {
490-
return util.Uint256{}, ErrConnectionLost
503+
return util.Uint256{}, nil, ErrConnectionLost
491504
}
492505

493506
alphabetList, err := c.notary.alphabetSource() // prepare arguments for test invocation
494507
if err != nil {
495-
return util.Uint256{}, err
508+
return util.Uint256{}, nil, err
496509
}
497510

498511
var until uint32
@@ -502,18 +515,18 @@ func (c *Client) notaryInvoke(committee, invokedByAlpha bool, contract util.Uint
502515
} else {
503516
until, err = c.notaryTxValidationLimit(conn)
504517
if err != nil {
505-
return util.Uint256{}, err
518+
return util.Uint256{}, nil, err
506519
}
507520
}
508521

509522
cosigners, err := c.notaryCosigners(invokedByAlpha, alphabetList, committee)
510523
if err != nil {
511-
return util.Uint256{}, err
524+
return util.Uint256{}, nil, err
512525
}
513526

514527
nAct, err := notary.NewActor(conn.client, cosigners, c.acc)
515528
if err != nil {
516-
return util.Uint256{}, err
529+
return util.Uint256{}, nil, err
517530
}
518531

519532
mainH, fbH, untilActual, err := nAct.Notarize(nAct.MakeTunedCall(contract, method, nil, func(r *result.Invoke, t *transaction.Transaction) error {
@@ -530,8 +543,10 @@ func (c *Client) notaryInvoke(committee, invokedByAlpha bool, contract util.Uint
530543

531544
return nil
532545
}, args...))
546+
547+
var res *state.AppExecResult
533548
if await {
534-
_, err = nAct.WaitSuccess(mainH, fbH, untilActual, err)
549+
res, err = nAct.WaitSuccess(mainH, fbH, untilActual, err)
535550
}
536551

537552
if err != nil {
@@ -544,10 +559,10 @@ func (c *Client) notaryInvoke(committee, invokedByAlpha bool, contract util.Uint
544559
zap.String("fallback_hash", fbH.StringLE()),
545560
)
546561

547-
return util.Uint256{}, nil
562+
return util.Uint256{}, res, nil
548563
}
549564

550-
return util.Uint256{}, err
565+
return util.Uint256{}, nil, err
551566
}
552567

553568
c.logger.Debug("notary request invoked",
@@ -556,7 +571,7 @@ func (c *Client) notaryInvoke(committee, invokedByAlpha bool, contract util.Uint
556571
zap.String("tx_hash", mainH.StringLE()),
557572
zap.String("fallback_hash", fbH.StringLE()))
558573

559-
return mainH, nil
574+
return mainH, res, nil
560575
}
561576

562577
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
@@ -14,6 +14,7 @@ const (
1414
GetTakenSpaceByUserMethod = "getTakenSpaceByUser"
1515
GetContainerQuotaMethod = "containerQuota"
1616
GetUserQuotaMethod = "userQuota"
17+
AddContainerStructsMethod = "addStructs"
1718
)
1819

1920
// 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
@@ -199,3 +199,23 @@ func RestorePutContainerEACLRequest(notaryReq event.NotaryEvent) (event.Event, e
199199

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

0 commit comments

Comments
 (0)