-
Notifications
You must be signed in to change notification settings - Fork 17
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
Conversation
316ab70
to
c68c652
Compare
c68c652
to
2ae28ee
Compare
There was a problem hiding this 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?
contracts/container/contract.go
Outdated
common.CheckAlphabetWitness(multiaddr) | ||
|
||
for _, node := range nodes { | ||
pk := node[2 : 2+interop.PublicKeyCompressedLen] // public key in V2 marshaled node info |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
contracts/container/contract.go
Outdated
@@ -66,6 +67,8 @@ const ( | |||
containerKeyPrefix = 'x' | |||
ownerKeyPrefix = 'o' | |||
deletedKeyPrefix = 'd' | |||
nodesPrefix = 'n' | |||
nextEpochNodesPrefix = 'c' |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
contracts/container/contract.go
Outdated
// 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) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
contracts/container/contract.go
Outdated
|
||
// 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 |
There was a problem hiding this comment.
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:]...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
newKey := append([]byte{nodesPrefix}, newNode.key[1:]...) | |
newKey := append([]byte{nodesPrefix}, newNode.key[len(nextEpochNodesPrefix):]...) |
or also make a const
There was a problem hiding this comment.
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
tests/container_test.go
Outdated
stack, err := c.TestInvoke(t, "nodes", cID[:]) | ||
require.NoError(t, err) | ||
|
||
iter, ok := stack.Pop().Value().(*storage.Iterator) |
There was a problem hiding this comment.
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
?
contracts/container/contract.go
Outdated
@@ -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 |
There was a problem hiding this comment.
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].
There was a problem hiding this comment.
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
tests/container_test.go
Outdated
require.ElementsMatch(t, containerList, fromContract) | ||
|
||
// clean up container | ||
c.Invoke(t, stackitem.Null{}, "commitContainerListUpdate", cID[:]) |
There was a problem hiding this comment.
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/contract.go
Outdated
@@ -66,6 +67,8 @@ const ( | |||
containerKeyPrefix = 'x' | |||
ownerKeyPrefix = 'o' | |||
deletedKeyPrefix = 'd' | |||
nodesPrefix = 'n' | |||
nextEpochNodesPrefix = 'c' |
There was a problem hiding this comment.
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.
contracts/container/contract.go
Outdated
// 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) { |
There was a problem hiding this comment.
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
// 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) { |
There was a problem hiding this comment.
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
.
2ae28ee
to
a52d1eb
Compare
@cthulhu-rider, yes, if a list is too big we have to make it in more than one call, the stack is limited.
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. |
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets also update
neofs-contract/contracts/container/doc.go
Line 72 in 2059417
Contract storage model. |
contracts/container/contract.go
Outdated
common.CheckAlphabetWitness(multiaddr) | ||
|
||
counter := 0 | ||
counterKey := append([]byte{nextEpochNodesPrefix, 'i'}, cID...) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
contracts/container/contract.go
Outdated
storage.Put(ctx, newKey, newNode.val) | ||
} | ||
if newCounter := storage.Get(ctx, newCounterKey); newCounter != nil { | ||
storage.Put(ctx, oldCounterKey, storage.Get(ctx, newCounterKey)) |
There was a problem hiding this comment.
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
storage.Put(ctx, oldCounterKey, storage.Get(ctx, newCounterKey)) | |
storage.Put(ctx, oldCounterKey, newCounter) |
not working?
There was a problem hiding this comment.
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
contracts/container/contract.go
Outdated
if newCounter := storage.Get(ctx, newCounterKey); newCounter != nil { | ||
storage.Put(ctx, oldCounterKey, storage.Get(ctx, newCounterKey)) | ||
} else { | ||
storage.Delete(ctx, newCounterKey) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
contracts/container/contract.go
Outdated
// 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))) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
a52d1eb
to
948f97b
Compare
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. |
Hm, did not even know we have it. Updated. |
contracts/container/contract.go
Outdated
common.CheckAlphabetWitness(multiaddr) | ||
|
||
counter := 0 | ||
counterKey := append([]byte{nextEpochNodesPrefix, 'i'}, cID...) |
There was a problem hiding this comment.
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.
contracts/container/contract.go
Outdated
common.CheckAlphabetWitness(multiaddr) | ||
|
||
counter := 0 | ||
counterKey := append([]byte{nextEpochNodesPrefix, 'i'}, cID...) |
There was a problem hiding this comment.
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.
948f97b
to
d1675a2
Compare
e9c8966
to
d344274
Compare
62b22e9
to
f0f04ad
Compare
@roman-khimov, recheck carefully, please, this is the 4th time i redo the PR, it may have stupid mistakes. |
f0f04ad
to
7b231b8
Compare
7b231b8
to
b7d6207
Compare
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>
b7d6207
to
660756f
Compare
Follows nspcc-dev/neofs-contract#438. Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
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>
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>
Follows nspcc-dev/neofs-contract#438. Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
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>
Follows nspcc-dev/neofs-contract#438. Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
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>
Follows nspcc-dev/neofs-contract#438. Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
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>
Follows nspcc-dev/neofs-contract#438. Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
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>
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.