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

feature request: add button to balance all existing channels in current network #831

Open
uwla opened this issue Jan 24, 2024 · 9 comments · May be fixed by #859
Open

feature request: add button to balance all existing channels in current network #831

uwla opened this issue Jan 24, 2024 · 9 comments · May be fixed by #859
Labels
enhancement New feature or request up for grabs Anyone can work on this

Comments

@uwla
Copy link
Contributor

uwla commented Jan 24, 2024

Is your feature request related to a problem? Please describe.
Yes, the problem is: sometimes we need balanced channels to do some stuff while testing and developing locally, but balancing them manually takes precious time.

Describe the solution you'd like
A button that we can click and have the channels balanced automatically (in the background by polar).

Suggestion

Here is a pseudo-code to balance the channels:

button.onClick((event) => {
  for (channel of getChannels()) {
    amountPeer1 = channel.getAmountPeer1()
    amountPeer2 = channel.getAmountPeer2()
    if (amountPeer1 == amountPeer2)
      continue;
    if (amountPeer 1 > amountPeer2) {
      invoice = channel.generateInvoiceForPeer2({ amount: (amountPeer1-amountPeer)/2 })
      channel.payInvoiceToPeer2(invoice)
    } else {
      // same thing as above
    }
  }
})

I can try do this, but some guidance is welcomed

@uwla uwla added the enhancement New feature or request label Jan 24, 2024
@jamaljsr jamaljsr added the up for grabs Anyone can work on this label Feb 9, 2024
@jamaljsr
Copy link
Owner

jamaljsr commented Feb 9, 2024

That's a pretty cool idea. No objections from me if you want to implement this.

@uwla
Copy link
Contributor Author

uwla commented Feb 9, 2024

I'll implement it.
I have not done it yet, but I'll do it 👍

@Anyitechs
Copy link

Hi @uwla, have you been able to work on this? I can help with that immediately if you have something else at hand.

CC @jamaljsr

@uwla
Copy link
Contributor Author

uwla commented Feb 27, 2024

Hi @uwla, have you been able to work on this? I can help with that immediately if you have something else at hand.

CC @jamaljsr

Hello. I was busy and could not do it. I think I'll be do able to implement the feature by the weekend, otherwise I'll let you know

@uwla
Copy link
Contributor Author

uwla commented Mar 1, 2024

Hi @jamaljsr and @Anyitechs.

I think I need help here, but more with the development stuff.

In the past, I was able to build Polar locally and to run the development server with hot-reload. But, I'm currently not being able to do so neither locally neither using Dev Containers. I get the following errors:

$ cross-env CI=false PUBLIC_URL=./ rescripts build
node:internal/modules/cjs/loader:575
      throw e;
      ^

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './lib/tokenize' is not defined by "exports" in /app/node_modules/postcss-safe-parser/node_modules/postcss/package.json
    at new NodeError (node:internal/errors:399:5)
    at exportsNotFound (node:internal/modules/esm/resolve:261:10)
    at packageExportsResolve (node:internal/modules/esm/resolve:591:9)
    at resolveExports (node:internal/modules/cjs/loader:569:36)
    at Module._findPath (node:internal/modules/cjs/loader:643:31)
    at Module._resolveFilename (node:internal/modules/cjs/loader:1068:27)
    at Module._load (node:internal/modules/cjs/loader:928:27)
    at Module.require (node:internal/modules/cjs/loader:1149:19)
    at require (node:internal/modules/helpers:121:18)
    at Object.<anonymous> (/app/node_modules/postcss-safe-parser/lib/safe-parser.js:1:17) {
  code: 'ERR_PACKAGE_PATH_NOT_EXPORTED'
}

Node.js v20.1.0

after troubleshooting, I updated Node version to the latest stable using NVM. It solved the error but I got a new one:

Creating an optimized production build...
Error: error:0308010C:digital envelope routines::unsupported
    at new Hash (node:internal/crypto/hash:69:19)
    at Object.createHash (node:crypto:138:10)
    at module.exports (/app/node_modules/webpack/lib/util/createHash.js:135:53)
    at NormalModule._initBuildHash (/app/node_modules/webpack/lib/NormalModule.js:417:16)
    at handleParseError (/app/node_modules/webpack/lib/NormalModule.js:471:10)
    at /app/node_modules/webpack/lib/NormalModule.js:503:5
    at /app/node_modules/webpack/lib/NormalModule.js:358:12
    at /app/node_modules/loader-runner/lib/LoaderRunner.js:373:3
    at iterateNormalLoaders (/app/node_modules/loader-runner/lib/LoaderRunner.js:214:10)
    at iterateNormalLoaders (/app/node_modules/loader-runner/lib/LoaderRunner.js:221:10)
    at /app/node_modules/loader-runner/lib/LoaderRunner.js:236:3
    at runSyncOrAsync (/app/node_modules/loader-runner/lib/LoaderRunner.js:130:11)
    at iterateNormalLoaders (/app/node_modules/loader-runner/lib/LoaderRunner.js:232:2)
    at Array.<anonymous> (/app/node_modules/loader-runner/lib/LoaderRunner.js:205:4)
    at Storage.finished (/app/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:55:16)
    at /app/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:91:9
/app/node_modules/react-scripts/scripts/build.js:19
  throw err;

After more troubleshooting, I exported export NODE_OPTIONS=--openssl-legacy-provider, which solved the error but got a new one:

$ cross-env CI=false PUBLIC_URL=./ rescripts build
Creating an optimized production build...
One of your dependencies, babel-preset-react-app, is importing the
"@babel/plugin-proposal-private-property-in-object" package without
declaring it in its dependencies. This is currently working because
"@babel/plugin-proposal-private-property-in-object" is already in your
node_modules folder for unrelated reasons, but it may break at any time.

babel-preset-react-app is part of the create-react-app project, which
is not maintianed anymore. It is thus unlikely that this bug will
ever be fixed. Add "@babel/plugin-proposal-private-property-in-object" to
your devDependencies to work around this error. This will make this message
go away.
  
Failed to compile.

/app/src/components/App.tsx
TypeScript error in /app/src/components/App.tsx(14,6):
'StoreProvider' cannot be used as a JSX component.
  Its type 'typeof StoreProvider' is not a valid JSX element type.
    Type 'typeof StoreProvider' is not assignable to type 'new (props: any, deprecatedLegacyContext?: any) => Component<any, any, any>'.
      Construct signature return types 'StoreProvider<any>' and 'Component<any, any, any>' are incompatible.
        The types returned by 'render()' are incompatible between these types.
          Type 'React.ReactNode' is not assignable to type 'import("/app/node_modules/@types/react-dom/node_modules/@types/react/index").ReactNode'.
            Type '{}' is not assignable to type 'ReactNode'.  TS2786

    12 |   return (
    13 |     // store provider for easy-peasy hooks
  > 14 |     <StoreProvider store={store}>
       |      ^
    15 |       {/* react-redux provider for router state */}
    16 |       <Provider store={store as any}>
    17 |         {/* connected-react-router  */}

This one got me stuck.

I do want to implement this feature for Polar, but I'm not being able to compile or launch a development server.

I'll try setting up a Ubuntu VM and see if I can get it to work.

If any of you can help me here, I'll appreciate it!

@jamaljsr
Copy link
Owner

jamaljsr commented Mar 4, 2024

Make sure you're using the latest master branch. These issues should have been resolved by #827.

I just tested with a fresh clone of the repo using node v20.9.0 and it worked for me.

@uwla
Copy link
Contributor Author

uwla commented Mar 5, 2024

Thanks @jamaljsr it worked. Now I can work on this issue. I'll try to do some work this week, and finish it in the next week, because Im kinda busy this weekend.

@uwla
Copy link
Contributor Author

uwla commented Mar 11, 2024

Hi @jamaljsr. I'm almost finishing it, but I need a tip on how to get specific information.

For context, here is the function to balance a single channel:

const balanceChannel = async (
  channel: LightningNodeChannel,
  localNode: LightningNode,
  remoteNode: LightningNode,
  satsTolerance = 100,
) => {
  if (channel.status !== 'Open') {
    // TODO: warn about channel not opened.
    return;
  }

  if (remoteNode === undefined || remoteNode === null) {
    // TODO: warn about remote node being null.
    return;
  }

  const localBalance = Number(channel.localBalance);
  const remoteBalance = Number(channel.remoteBalance);
  const balanceDifference = localBalance - remoteBalance;

  // If the balance difference in satoshis is too small, we ignore it.
  if (Math.abs(balanceDifference) < satsTolerance) {
    return;
  }

  // The source node pays an invoice to the target node, in order to balance the channel.
  const src = balanceDifference > 0 ? localNode : remoteNode;
  const target = balanceDifference > 0 ? remoteNode : localNode;

  const invoice = await getNodeLightningService(target).createInvoice(
    target,
    balanceDifference,
  );

  await getNodeLightningService(src).payInvoice(src, invoice);
};

Notice we need the remoteNode because it creates the invoice.

Here is how the AutoBalanceButton currently looks like:

const AutoBalanceButton: React.FC<Props> = ({ network }) => {
  const handleClick = async () => {
    const lnNodes = network.nodes.lightning;
    const channels = {} as { [key: string]: ChannelInfo };

    for (const node of lnNodes) {
      const lightningService = getNodeLightningService(node);
      const nodeChannels = await lightningService.getChannels(node);

      for (const channel of nodeChannels) {
        const { uniqueId } = channel;

        // Add the channel, using current node as local node.
        if (!Object(channels).hasOwnProperty(uniqueId)) {
          channels[uniqueId] = {
            channel: channel,
            localNode: node,
          } as ChannelInfo;
        } else {
          // Channel already exists, so current node is treated as remote node.
          channels[uniqueId].remoteNode = node;
        }
      }
    }

    await autoBalanceChannels(Object.values(channels));
  };

  return <Button onClick={handleClick}>Auto Balance channels</Button>;
};

It is not working because the method getChannels of the interface LightningService will only return the outgoing channels, not the ingoing channels. Due to this, I cannot assign the remoteNode to the ChannelInfo (whose interface is { channel: Channel, localNode: LightningNode, remoteNode: LightningNode }) because the channels array is empty and I cannot loop over it.

Can you tell me which function, class or methdo I can use to get the LightningNode remote node of a LightningChannel channel ?

@jamaljsr
Copy link
Owner

Yes, the LightningService will only return the channels that were opened by the supplied node. This was the simpler approach to avoid creating duplicate links in the chart. There is an existing function (updateChartFromNodes) that uses the data from the lightning store to recreate the nodes & links in the chart based on the currently opened channels. I'd imagine that you could do something very similar to balance the channels. By only looking at the outbound channels for each node, you can be sure that you only balance each channel once. If you got all of the channels for each node, then you'd need to keep track of which channels you've already balanced or ensure that the channel balances were continually updated after each iteration.

I hope this helps, but let me know if you have more questions.

uwla added a commit to uwla/polar that referenced this issue Mar 15, 2024
Solves jamaljsr#831.

NOTE: work in progress, implementation not ready yet.
@uwla uwla linked a pull request Mar 15, 2024 that will close this issue
uwla added a commit to uwla/polar that referenced this issue Mar 23, 2024
@jamaljsr jamaljsr linked a pull request May 3, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request up for grabs Anyone can work on this
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants