Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

container: add container lists to the contract #438

Merged
merged 1 commit into from
Oct 14, 2024

Conversation

carpawell
Copy link
Member

It must be handled by the Alphabet and be updated after every epoch counter increase. Updating is done in two stage (filling a container list and commiting it) to prevent any stack size/memory restrictions. Closes #412.

@carpawell carpawell requested a review from AnnaShaleva October 2, 2024 07:50
@carpawell carpawell self-assigned this Oct 2, 2024
@carpawell carpawell force-pushed the feat/add-container-list branch from 316ab70 to c68c652 Compare October 2, 2024 07:54
@carpawell carpawell force-pushed the feat/add-container-list branch from c68c652 to 2ae28ee Compare October 2, 2024 07:59
Copy link
Contributor

@cthulhu-rider cthulhu-rider left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lux gut overall

@carpawell will there be multiple Add's between Commit's? And might we need deletions?

common.CheckAlphabetWitness(multiaddr)

for _, node := range nodes {
pk := node[2 : 2+interop.PublicKeyCompressedLen] // public key in V2 marshaled node info
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd follow 2636de0 and make consts. Moreover, given that this is a single protocol element, I would share the approach

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

depends on the final solution about what exact we store in the contract. will change if it is still proto NodeInfo

@@ -66,6 +67,8 @@ const (
containerKeyPrefix = 'x'
ownerKeyPrefix = 'o'
deletedKeyPrefix = 'd'
nodesPrefix = 'n'
nextEpochNodesPrefix = 'c'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couldnt the prefix match with estimateKeyPrefix cause potential collisions? i'd try to make prefixes as non-overlapping as possible

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, prefixes should be distinct. We do have some long ones for historic reasons, but new prefixes must not collide with them.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i tried to fit as much public key in key as possible (cid + pk is already more than contract can have as a key). are there any ideas how to store a list of smth that has 65-byte unique identifier in contract?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a separate question, here you can just change the prefix.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, this question only about the colision, ok, fixed

// method must be called only when a container list is changed, otherwise
// nothing should be done.
// Call must be signed by the Alphabet nodes.
func AddNextEpochNodes(cID []byte, nodes [][]byte) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we should clarify what exactly these byte arrays are and verify them 1st. cID must be exactl 32B, each of nodes must be at least 2+interop.PublicKeyCompressedLen

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have just keys there? Everyone knows the network map anyway (can get details based on key). In fact, even a shorter address handle can be used, but this would require a bit more work (so keys are OK).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, please use more specific types. hash256 is much better than just []byte.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we should clarify what exactly these byte arrays are and verify them 1st

Also, please use more specific types. hash256 is much better than just []byte.

@roman-khimov, if it is hash256, can it be ensured by the VM? or still we need to check it manually?

Can we have just keys there?

we have discussed it a little and i thought that we make it clear that it is useless for current nodes and IR. if you need a container, then you need some ways to communicate with it, not just know its keys. it can be useful for the future things (#413), is this PR for contract-side calculations only?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it is hash256, can it be ensured by the VM?

No, neo-project/neo#1891.

if you need a container, then you need some ways to communicate with it

Well, you can get a node by key. And full node info can take quite some space. But let's say it's OK for now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, neo-project/neo#1891.

fresh thing. ok, type is type, fixed

But let's say it's OK for now.

what exacty OK? this PR or storing keys only? i like storing only keys more. just want to be sure we all understand SN and IR will not be using it in the current code, only for new contract features. they will likely use a single network request, a single calculation (placement) and caching, not this PR

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're targeting meta only then full node info isn't needed. But eventually I think this list can be used by other code since no one cares about placement policy, everyone just needs a node list to work with. Full node info can still be provided if this contract is to store keys only, not immediately, but after netmap refactorings. So overall keys only are OK.


// CommitContainerListUpdate commits container list changes made by
// [AddNextEpochNodes] calls in advance. If no [AddNextEpochNodes] have been
// made, it clears container list. Mades "ContainerUpdate" notification with
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes or throws


storage.Delete(ctx, newNode.key[:])

newKey := append([]byte{nodesPrefix}, newNode.key[1:]...)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
newKey := append([]byte{nodesPrefix}, newNode.key[1:]...)
newKey := append([]byte{nodesPrefix}, newNode.key[len(nextEpochNodesPrefix):]...)

or also make a const

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not possible for now (it is a char literally, no length), likely be resolved automatically when we resolve other threads

stack, err := c.TestInvoke(t, "nodes", cID[:])
require.NoError(t, err)

iter, ok := stack.Pop().Value().(*storage.Iterator)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this into iteratorToBytesArray and call it stackToByteArrays?

@@ -491,6 +494,67 @@ func List(owner []byte) [][]byte {
return list
}

// AddNextEpochNodes adds container members for the next epoch. Changes must be
// committed with [CommitContainerListUpdate]. Without submitting, it is a no-op
Copy link
Contributor

@cthulhu-rider cthulhu-rider Oct 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i know what u mean, but no-op can cause misunderstaning: contract storage is changed, GAS spent, although user sees no side effect through the API. I'd say AddNextEpochNodes accumulates passed nodes as container members for the next epoch to be committed using [CommitContainerListUpdate].

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i meant without submitting it is not possible to see any changes via public interfaces, it is an alphabet-only method, so gas and any other things are known and does not mean a lot. but don't mind your way

require.ElementsMatch(t, containerList, fromContract)

// clean up container
c.Invoke(t, stackitem.Null{}, "commitContainerListUpdate", cID[:])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1st i'd make one more addNextEpochNodes call with containerList subset + several completely new elements. And check that old elements are dropped. This is quite non-trivial with add-add-add... semantic sequence

contracts/container/config.yml Outdated Show resolved Hide resolved
@@ -66,6 +67,8 @@ const (
containerKeyPrefix = 'x'
ownerKeyPrefix = 'o'
deletedKeyPrefix = 'd'
nodesPrefix = 'n'
nextEpochNodesPrefix = 'c'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, prefixes should be distinct. We do have some long ones for historic reasons, but new prefixes must not collide with them.

// method must be called only when a container list is changed, otherwise
// nothing should be done.
// Call must be signed by the Alphabet nodes.
func AddNextEpochNodes(cID []byte, nodes [][]byte) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have just keys there? Everyone knows the network map anyway (can get details based on key). In fact, even a shorter address handle can be used, but this would require a bit more work (so keys are OK).

contracts/container/contract.go Outdated Show resolved Hide resolved
// method must be called only when a container list is changed, otherwise
// nothing should be done.
// Call must be signed by the Alphabet nodes.
func AddNextEpochNodes(cID []byte, nodes [][]byte) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, please use more specific types. hash256 is much better than just []byte.

contracts/container/contract.go Outdated Show resolved Hide resolved
go.mod Outdated Show resolved Hide resolved
contracts/container/config.yml Outdated Show resolved Hide resolved
contracts/container/config.yml Outdated Show resolved Hide resolved
tests/container_test.go Outdated Show resolved Hide resolved
@carpawell carpawell force-pushed the feat/add-container-list branch from 2ae28ee to a52d1eb Compare October 4, 2024 00:38
@carpawell
Copy link
Member Author

will there be multiple Add's between Commit's?

@cthulhu-rider, yes, if a list is too big we have to make it in more than one call, the stack is limited.

And might we need deletions?

You mean add-then-delete case? Will it be useful? What cases? New methods should be used by the alphabet nodes once per epoch if some container has changed, not sure I understand if an alphabet node calculates a container, puts it, and then decides to change it in a single epoch.

@cthulhu-rider
Copy link
Contributor

You mean add-then-delete case? Will it be useful? What cases?

best answer is to ask questions huh :] delete question followed after multi-add one. I thought if there is "add" and no "set", where is "delete". Now i see that several "add" are essentially "set" so removals is not needed for now

Copy link
Contributor

@cthulhu-rider cthulhu-rider left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets also update

Contract storage model.

common.CheckAlphabetWitness(multiaddr)

counter := 0
counterKey := append([]byte{nextEpochNodesPrefix, 'i'}, cID...)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isnt this overcomplicated? We still have a good supply of prefixes. Collission, although unlikely, is possible

Copy link
Member Author

@carpawell carpawell Oct 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isnt this overcomplicated?

maybe. i did not want to have even more prefixes for the same feature, this is my suggestion. can add a dedicated counter prefix if @roman-khimov is also for it

Collission, although unlikely, is possible

to have problems we need either 31 of 32 bytes should match (with an offset) and the first one should be fixed i byte OR a container should have the first 31 bytes of i. it is very like "two different containers have the same ID" with almost the same kind of problems for NeoFS

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can avoid storing the counter if you're to take the last element of nextEpochNodesPrefix + cID which is easy with Backwards flag (https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/interop@v0.0.0-20241007161539-0968c3a81fb0/storage#FindFlags). But to do this you need to have counter serialized in BE form.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or you can avoid using counter with https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/interop@v0.0.0-20241007161539-0968c3a81fb0/contract#CreateStandardAccount. This would solve duplicate problem at the same time.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@roman-khimov, idea with Backwards is the best for me, i like it

But to do this you need to have counter serialized in BE form.

how can i ensure it? are there any contract side helpers for it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we have any, serialize+pad+reverse, maybe.

storage.Put(ctx, newKey, newNode.val)
}
if newCounter := storage.Get(ctx, newCounterKey); newCounter != nil {
storage.Put(ctx, oldCounterKey, storage.Get(ctx, newCounterKey))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to keep it?

and

Suggested change
storage.Put(ctx, oldCounterKey, storage.Get(ctx, newCounterKey))
storage.Put(ctx, oldCounterKey, newCounter)

not working?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to keep it?

technically no need, but there is a certain scheme: we have a counter and then this number of keys with counter suffixes. also can imagine requesting current number of container nodes, one Get may be simpler than searching for every entry
tbh, i did not even think about it, just moved all the "new" things to the "old" places. let it be dropped as not needed

if newCounter := storage.Get(ctx, newCounterKey); newCounter != nil {
storage.Put(ctx, oldCounterKey, storage.Get(ctx, newCounterKey))
} else {
storage.Delete(ctx, newCounterKey)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why if it is missing?

and shouldnt we always reset it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dropped eventually

and shouldnt we always reset it?

but why not? meaningless counter, yes it may be overwritten with the next container update but before it happenes it is just a garbage

// Call must be signed by the Alphabet nodes.
func CommitContainerListUpdate(cID interop.Hash256) {
if len(cID) != interop.Hash256Len {
panic(cst.ErrorInvalidContainerID + std.Itoa10(len(cID)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why differ with other methods?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed and added in test this call

@carpawell carpawell force-pushed the feat/add-container-list branch from a52d1eb to 948f97b Compare October 4, 2024 15:00
@carpawell
Copy link
Member Author

best answer is to ask questions huh :]

I cannot answer if i do not understand a question (even if it seems like a resolved one now, i still do not understand the original question and its resolution). You asked about deletion, i did not add this so i thought we did not need them. If you have some examples of its usage, i had to ask you to provide them, cause i did not see them. I hope this PR conveys my thoughts and my suggestions, otherwise, i need additional suggestions or change requests.

@carpawell
Copy link
Member Author

lets also update

Hm, did not even know we have it. Updated.

common.CheckAlphabetWitness(multiaddr)

counter := 0
counterKey := append([]byte{nextEpochNodesPrefix, 'i'}, cID...)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can avoid storing the counter if you're to take the last element of nextEpochNodesPrefix + cID which is easy with Backwards flag (https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/interop@v0.0.0-20241007161539-0968c3a81fb0/storage#FindFlags). But to do this you need to have counter serialized in BE form.

common.CheckAlphabetWitness(multiaddr)

counter := 0
counterKey := append([]byte{nextEpochNodesPrefix, 'i'}, cID...)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or you can avoid using counter with https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/interop@v0.0.0-20241007161539-0968c3a81fb0/contract#CreateStandardAccount. This would solve duplicate problem at the same time.

contracts/container/contract.go Outdated Show resolved Hide resolved
contracts/container/contract.go Outdated Show resolved Hide resolved
contracts/container/contract.go Outdated Show resolved Hide resolved
contracts/container/contract.go Outdated Show resolved Hide resolved
contracts/container/contract.go Show resolved Hide resolved
contracts/container/contract.go Outdated Show resolved Hide resolved
contracts/container/contract.go Outdated Show resolved Hide resolved
contracts/container/contract.go Outdated Show resolved Hide resolved
@carpawell
Copy link
Member Author

@roman-khimov, recheck carefully, please, this is the 4th time i redo the PR, it may have stupid mistakes.

@carpawell carpawell force-pushed the feat/add-container-list branch from f0f04ad to 7b231b8 Compare October 11, 2024 18:10
contracts/container/contract.go Outdated Show resolved Hide resolved
contracts/container/contract.go Outdated Show resolved Hide resolved
contracts/container/contract.go Outdated Show resolved Hide resolved
contracts/container/contract.go Outdated Show resolved Hide resolved
@carpawell carpawell force-pushed the feat/add-container-list branch from 7b231b8 to b7d6207 Compare October 13, 2024 17:37
It must be handled by the Alphabet and be updated after every epoch counter is
increased. Updating is done in two stage (filling a container list and commiting
it) to prevent any stack size/memory restrictions. Closes #412.

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
@carpawell carpawell force-pushed the feat/add-container-list branch from b7d6207 to 660756f Compare October 13, 2024 17:39
@roman-khimov roman-khimov merged commit 6a5813a into master Oct 14, 2024
8 checks passed
@roman-khimov roman-khimov deleted the feat/add-container-list branch October 14, 2024 07:19
carpawell added a commit to nspcc-dev/neofs-node that referenced this pull request Dec 10, 2024
Follows nspcc-dev/neofs-contract#438.

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
carpawell added a commit to nspcc-dev/neofs-node that referenced this pull request Dec 10, 2024
After nspcc-dev/neofs-contract#438 Container contract
now has API for container-side placement/verification operations. But building
placement vectors and updating when needed is still a complex operation that
should be done on a Go application side. This commit adds such a responsibility
for Alphabet nodes. For simplicity, updating is done every epoch and once for
_every_ container if netmap has been changed. Additional optimization can be
considered.

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
carpawell added a commit to nspcc-dev/neofs-node that referenced this pull request Dec 11, 2024
After nspcc-dev/neofs-contract#438 Container contract
now has API for container-side placement/verification operations. But building
placement vectors and updating when needed is still a complex operation that
should be done on a Go application side. This commit adds such a responsibility
for Alphabet nodes. For simplicity, updating is done every epoch and once for
_every_ container if netmap has been changed. Additional optimization can be
considered.

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
carpawell added a commit to nspcc-dev/neofs-node that referenced this pull request Dec 11, 2024
Follows nspcc-dev/neofs-contract#438.

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
carpawell added a commit to nspcc-dev/neofs-node that referenced this pull request Dec 11, 2024
After nspcc-dev/neofs-contract#438 Container contract
now has API for container-side placement/verification operations. But building
placement vectors and updating when needed is still a complex operation that
should be done on a Go application side. This commit adds such a responsibility
for Alphabet nodes. For simplicity, updating is done every epoch and once for
_every_ container if netmap has been changed. Additional optimization can be
considered.

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
carpawell added a commit to nspcc-dev/neofs-node that referenced this pull request Dec 11, 2024
Follows nspcc-dev/neofs-contract#438.

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
carpawell added a commit to nspcc-dev/neofs-node that referenced this pull request Dec 11, 2024
After nspcc-dev/neofs-contract#438 Container contract
now has API for container-side placement/verification operations. But building
placement vectors and updating when needed is still a complex operation that
should be done on a Go application side. This commit adds such a responsibility
for Alphabet nodes. For simplicity, updating is done every epoch and once for
_every_ container if netmap has been changed. Additional optimization can be
considered.

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
carpawell added a commit to nspcc-dev/neofs-node that referenced this pull request Dec 12, 2024
Follows nspcc-dev/neofs-contract#438.

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
carpawell added a commit to nspcc-dev/neofs-node that referenced this pull request Dec 12, 2024
After nspcc-dev/neofs-contract#438 Container contract
now has API for container-side placement/verification operations. But building
placement vectors and updating when needed is still a complex operation that
should be done on a Go application side. This commit adds such a responsibility
for Alphabet nodes. For simplicity, updating is done every epoch and once for
_every_ container if netmap has been changed. Additional optimization can be
considered.

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
carpawell added a commit to nspcc-dev/neofs-node that referenced this pull request Dec 23, 2024
Follows nspcc-dev/neofs-contract#438.

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
carpawell added a commit to nspcc-dev/neofs-node that referenced this pull request Dec 23, 2024
After nspcc-dev/neofs-contract#438 Container contract
now has API for container-side placement/verification operations. But building
placement vectors and updating when needed is still a complex operation that
should be done on a Go application side. This commit adds such a responsibility
for Alphabet nodes. For simplicity, updating is done every epoch and once for
_every_ container if netmap has been changed. Additional optimization can be
considered.

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Extend container contract with node lists
3 participants