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

Deprecate, archive, and forgettabout this... except maybe for VS Code? #15

Open
coolaj86 opened this issue Aug 7, 2024 · 0 comments
Open

Comments

@coolaj86
Copy link
Member

coolaj86 commented Aug 7, 2024

See dashpay/dashd-rpc#77.

The only advantage that this library offers over the much simpler solution is that the type hinting works in vim and VSCode and such.

I don't think that's it's worth the extra ONE THOUSAND lines.

There's probably some TypeScript trickery to overload the function type based on a constant as the first parameter that could do just as well as .d.ts or some such.

Update: Yep: dashpay/dashd-rpc#77 (comment)

Copied from the Linked Issue

After years of submitting bugfixes and refactoring this library a little at a time, I finally found out what it actually does - which is so simple by comparison to how this C++ to JS port (I assume) turned out (I assume by the bitcoin team) that it's difficult to believe.

I'm not even sure if the people who use this regularly know what does due to all of the complex and abstract metaprogramming obscuring the functionality (otherwise I imagine they wouldn't use it).

It's just a really, really complicated way to do an http call that's actually this simple:

DashRPC, in truth:

curl "$rpc_protocol://$rpc_hostname:$rpc_port/" \
    --user "$rpc_username:$rpc_password" \
    -H 'Content-Type: application/json' \
    --data-binary '{ "id": 37, "method": "getbestblock", "params": [] }'

DashRPC, as a JS function:

Here's the library reimplemented in just a few lines:

Source: DashTx.js

  /**
   * @param {String} basicAuthUrl - ex: https://api:[email protected]/
   *                                    http://user:pass@localhost:19998/
   * @param {String} method - the rpc, such as 'getblockchaininfo',
   *                          'getaddressdeltas', or 'help'
   * @param {...any} params - the arguments for the specific rpc
   *                          ex: rpc(url, 'help', 'getaddressdeltas')
   */
  async function rpc(basicAuthUrl, method, ...params) {
    let url = new URL(basicAuthUrl);
    let baseUrl = `${url.protocol}//${url.host}${url.pathname}`;
    let basicAuth = btoa(`${url.username}:${url.password}`);

    // typically http://localhost:19998/
    let payload = JSON.stringify({ method, params });
    let resp = await fetch(baseUrl, {
      method: "POST",
      headers: {
        Authorization: `Basic ${basicAuth}`,
        "Content-Type": "application/json",
      },
      body: payload,
    });

    let data = await resp.json();
    if (data.error) {
      let err = new Error(data.error.message);
      Object.assign(err, data.error);
      throw err;
    }

    return data.result;
  };

DashRPC, as a JS Lib:

Here's the library reimplemented with all the JavaScript niceties you'd want:

let baseUrl = `${protocol}://${host}:${port}/`;
let rpc = createRpcClient({ baseUrl, username, password });

let result = await rpc.request('getaddressbalance', [ "yhhZ1o9TsaJzh2YKA7qM5vD2BgjT5XffvK" ]);
function createRpcClient({ baseUrl, username, password }) {
  let basicAuth = btoa(`${username}:${password}`);

  async function request(rpcname, ...args) {
    rpcname = rpcname.toLowerCase();
    let id = getRandomId();
    let body = { id: id, method: rpcname, params: args };
    let payload = JSON.stringify(body);

    let resp = await fetch(rpcBaseUrl, {
      method: 'POST',
      headers: {
        'Authorization': `Basic ${basicAuth}`,
        'Content-Type': 'application/json'
      },
      body: payload,
    });

    let data = await resp.json();
    if (data.error) {
      let err = new Error(data.error.message);
      Object.assign(err, data.error);
      throw err;
    }

    let result = data.result || data;
    return result;
  }

  return {
    request,
  };
}

// not even sure if this range is required
function getRandomId() {
  let f64 = Math.random() * 100000;
  let i32 = Math.round(f64);
  return i32;
}

Adding some flourish

init()

And if you wanted to make it convenient, you could add an init() method that loops until E_IN_WARMUP disappears:

let baseUrl = `${protocol}://${host}:${port}/`;
let rpc = createRpcClient({ baseUrl, username, password });

await rpc.init();
let result = await rpc.request('getaddressbalance', [ "yhhZ1o9TsaJzh2YKA7qM5vD2BgjT5XffvK" ]);
function createRpcClient({ baseUrl, username, password }) {

  // ...

  const E_IN_WARMUP = -28;

  async function init() {
    for (;;) {
      let result = await request('getchaintips', []).catch(function (err) {
        if (err.code === E_IN_WARMUP) {
          return null;
        }
        throw err;
      });
      if (result) {
        break;
      }
    }
  }

  return {
    init,
    request,
  }
}

Type Hinting

The argument could be made that this provides some type hinting, but it doesn't even work with tsc or vim or VSCode.

It's done in such a bespoke way, that can't be auto-generated to keep up with the actual Dash RPCs, so it's worse to have it than to not having it at all.

If there were some machine-friendly JSON file for type hints, it could very simply be applied to each argument at the time each request is made:

  function request(rpcname, ...args) {
    rpcname = rpcname.toLowerCase();
    assertTypes(rpcname, args);

    // ...
  }

  // the hundred+ different rpc names and types go here 
  let allTypeHints = { 'getfoothing': [ [ 'number' ], [ 'number', 'string', null ] ] };

  function assertTypes(rpcname, args) {
    let typeHints = allTypeHints[rpcname];

    for (let i = 0; i < args.length; i += 1) {
      let arg = args[i];
      let typeHint = typeHints[i];
      assertType(typeHint, arg, i);
    }
  }

  function assertType(typeHint, arg, i) {
    if (!typeHint) { // may be a new extra arg we don't know yet
      return;
    }

    let thisType = typeof arg;
    let isType = typeHint.includes(typeHint);
    if (isType) { // is a known arg of a known type
      return;
    }

    let isNullish = !arg && 'boolean' !== thisType && typeHint.includes(null);
    if (isNullish) { // is an optional arg
      return;
    }

    throw new Error(`expected params[${i}] to be one of [${typeHint}], but got '${thisType}'`);
  }

Alternatively the type hinting could be generated as a build step... but it would result it thousands of extra lines of code (I know because I experimented with it already: https://github.com/dashhive/DashRPC.js/blob/v20.0.0/scripts/generate.js)

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

No branches or pull requests

1 participant