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

failure in elementsIsSuperset({x, y, z}, {z}) #2588

Closed
dckc opened this issue Oct 17, 2024 · 2 comments · Fixed by #2597
Closed

failure in elementsIsSuperset({x, y, z}, {z}) #2588

dckc opened this issue Oct 17, 2024 · 2 comments · Fixed by #2597
Assignees
Labels
bug Something isn't working

Comments

@dckc
Copy link
Contributor

dckc commented Oct 17, 2024

Describe the bug

An ERTP withdraw of {z} from a purse with a balance of {x, y, z} failed.

I'm abbreviating makeCopySet(...items) using traditional {...items} set notation.

Investigation led to a failure in elementsIsSuperset({x, y, z}, {z}).

Steps to reproduce

test('{x, y, z} is superset of {z}', t => {
  const x = { instance: Remotable('Alleged: fooo'), description: 'fraz' };
  const y = { instance: Remotable('Alleged: fooo'), description: 'fraz' };
  const z = { instance: Remotable('Alleged: fooo'), description: 'alan' };

  const mainList = harden([x, y, z]);
  const withdraw = harden([z]);

  t.true(elementsIsSuperset(mainList, withdraw));
});

Expected behavior

elementsIsSuperset({x, y, z}, {z}) always returns true for keys x, y, and z.

Platform environment

The original environment was a ci log of an a3p test.

It reproduces in 4406f5d

Additional context

some diagnosis to follow

@dckc dckc added the bug Something isn't working label Oct 17, 2024
@dckc
Copy link
Contributor Author

dckc commented Oct 17, 2024

@frazarshad writes:

the issue is in the elementsIsSuperset function in this file
https://github.com/endojs/endo/blob/master/packages/patterns/src/keys/merge-set-operators.js

In cases where we have a set of invitations with duplicate descriptions, the function fails to mark it as a subset.

we can verify by adding this test to packages/patterns/test/copySet.test.js file of endojs

test('test', t => {
  const r1 = Remotable('Alleged: fooo');
  const r0 = Remotable('Alleged: fooo');
  const r2 = Remotable('Alleged: fooo');

  const mainList = harden([
    {
      instance: r0,
      description: 'fraz',
    },
    {
      instance: r2,
      description: 'fraz',
    },
    {
      instance: r1,
      description: 'alan',
    },
  ]);

  const withdraw = harden([mainList[2]]);

  t.true(elementsIsSuperset(mainList, withdraw));
});

Longer explanation

this code is triggered through the purse's withdraw function when it tries to verify whether the item we are withdrawing is a subset of the items in the purse.

the elementsIsSuperset function here uses mergeify function that is responsible for creating an iterator. Each returned item in the iterator is an element of the original two arrays along with flags that represent if the element was present in the left or right array or both.
so something like this:

[
    [elem1, 1n, 0n],
    [elem2, 0n, 1n],
    ...
]

in case the elements are invitations, which is a list of object with strings and remotables, it compares them by assigning tag numbers to the remotables.
This tagging is done at the time of comparison so any remotable that is compared (or seen) later has a larger tag number.
This code works as expected if all remotables are untagged before the comparison starts

The problem arises when we pass invitations having the same description.
During the initial parts of mergeify when the two arrays are being converted into iterables using windowResort , this line runs to check if there are any duplicates in the array:

while (j < length && rankCompare(value, elements[j]) === 0) {

rankCompare here is being used to compare two elements of the purse to check for duplicates. the problem is that rankCompare cant tell apart remotable objects, so all remotables are considered equal for it.
therefore if two invitations with the same description are present such as:

{
  instance: Object [Alleged: InstanceHandle] {},
  installation: Object [Alleged: BundleIDInstallation] {},
  handle: Object [Alleged: InvitationHandle] {},
  description: 'oracle invitation'
} 
{
  instance: Object [Alleged: InstanceHandle] {},
  installation: Object [Alleged: BundleIDInstallation] {},
  handle: Object [Alleged: InvitationHandle] {},
  description: 'oracle invitation'
}

It will consider them equal, thus it will move to this line

const resorted = sortByRank(similarRun, fullCompare);

which performs a fullCompare , by running a full compare, we esentially start tagging the remotables of the similar named invitations thereby polluting the state that we require for the rest of mergeify to work properly

@dckc
Copy link
Contributor Author

dckc commented Oct 17, 2024

@gibson042 writes:

this is amazing! I'll see what I can do about a fix in endo

I'm inclined to assign this to him on that basis, but I'm not quite sure that I should. @gibson042 please assign yourself, unless there's some reason not to.

cc @aj-agoric

@gibson042 gibson042 self-assigned this Oct 17, 2024
gibson042 added a commit that referenced this issue Oct 21, 2024
…irst remotable

To be refinable into a total order that distinguishes remotables,
`compareRank` must consider `[r1, 'x']` vs. `[r2, 'y']` as a tie rather
than as equivalent to `[0, 'x']` vs. `[0, 'y']`, in case r1 vs. r2 ends
up comparing differently than 'x' vs. 'y'.

Fixes #2588
gibson042 added a commit that referenced this issue Oct 21, 2024
…irst remotable

To be refinable into a total order that distinguishes remotables,
`compareRank` must consider `[r1, 'x']` vs. `[r2, 'y']` as a tie rather
than as equivalent to `[0, 'x']` vs. `[0, 'y']`, in case r1 vs. r2 ends
up comparing differently than 'x' vs. 'y'.

Fixes #2588
gibson042 added a commit that referenced this issue Oct 21, 2024
…irst remotable

To be refinable into a total order that distinguishes remotables,
`compareRank` must consider `[r1, 'x']` vs. `[r2, 'y']` as a tie rather
than as equivalent to `[0, 'x']` vs. `[0, 'y']`, in case r1 vs. r2 ends
up comparing differently than 'x' vs. 'y'.

Fixes #2588
gibson042 added a commit that referenced this issue Oct 22, 2024
…irst remotable

To be refinable into a total order that distinguishes remotables,
`compareRank` must consider `[r1, 'x']` vs. `[r2, 'y']` as a tie rather
than as equivalent to `[0, 'x']` vs. `[0, 'y']`, in case r1 vs. r2 ends
up comparing differently than 'x' vs. 'y'.

Fixes #2588
gibson042 added a commit that referenced this issue Oct 22, 2024
…irst remotable

To be refinable into a total order that distinguishes remotables,
`compareRank` must consider `[r1, 'x']` vs. `[r2, 'y']` as a tie rather
than as equivalent to `[0, 'x']` vs. `[0, 'y']`, in case r1 vs. r2 ends
up comparing differently than 'x' vs. 'y'.

Fixes #2588
kriskowal added a commit that referenced this issue Oct 22, 2024
# `@endo/marshal` v1.6.0

- `compareRank` now short-circuits upon encountering remotables to
compare, considering the inputs to be tied for the same rank regardless
of what would otherwise be visited later in their respective data
structures. This ensures that a `fullCompare` which does distinguish
remotables will be a refinement of `compareRank`, rather than
disagreeing about whether or not two values share a rank
([#2588](#2588)).

This change is a bug fix for all purposes off-chain, but will frustrate
deterministic replay. So, because of this change and probably many
others, the supervisor bundle of vats on chain will need to be created
from historical versions, not according to the semantic version of the
library.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants