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

Accept multiple signature requests in single transaction #880

Closed
volovyks opened this issue Oct 9, 2024 · 23 comments · Fixed by #909
Closed

Accept multiple signature requests in single transaction #880

volovyks opened this issue Oct 9, 2024 · 23 comments · Fixed by #909
Assignees
Labels
Emerging Tech Emerging Tech flying formation at Pagoda Near BOS NEAR BOS team at Pagoda

Comments

@volovyks
Copy link
Collaborator

volovyks commented Oct 9, 2024

MPC system should accept multiple payloads for signing. Obviously we can accept a longer TX time, and pay more, but it seems like most teams working with Bitcoin UTXOs are scratching their heads trying to come up with solutions for how to spend multiple UTXOs.

Each UTXO to spend requires it's own signature. So with the current configuration of MPC we can really only spend from one UTXO.

Possible solutions outside of the MPC accepting multiple payloads would be:

  • chain promise calls from a contract CONS: derived accounts are based on contract, have to make a self call to contract before returning the parallel calls made to MPC back to client
  • make multiple calls from client CONS: wallet / user needs to sign multiple TXs

Seeing as how everyone is going to want to do this sooner than later, for Bitcoin UTXOs and other applications, I think it should be a priority.

(requested by @mattlockyer)

@volovyks volovyks added Near BOS NEAR BOS team at Pagoda Emerging Tech Emerging Tech flying formation at Pagoda labels Oct 9, 2024
@volovyks
Copy link
Collaborator Author

volovyks commented Oct 9, 2024

@mattlockyer I agree that we should add this feature. Ideally, it should be incorporated into the contract API:

pub fn sign(&mut self, requests: Vec<SignRequest>) -> Result<near_sdk::Promise, Error>

I'm unsure whether we can make it synchronous with the current yield/resume design, but an asynchronous version should be feasible, though it may require more effort than it initially seems.

@mattlockyer
Copy link

@volovyks I was just experimenting calling the MPC contract with multiple functionCall actions per transaction.

It behaves a bit oddly. Basically sign_helper is called 3 times, once for each functionCall action (that call sign on MPC contract).

Then things get weird. clear_state_on_finish and return_signature_on_finish are only called once. The client only receives 1 status and SuccessValue for the transaction. Despite having 3 functionCall actions attached.

See the following result:

https://testnet.nearblocks.io/txns/6Y3sWjnrytHuTXL3ELkQkdbzbnyeBEQzJpZn2q5B4pAj#enhanced

If there is a way to process all 3 actions independently and return the array of promise results to the client, that would be ideal.

@volovyks
Copy link
Collaborator Author

@mattlockyer are you making a batch transaction with 3 function_call sign actions?
This can be a good way to achieve the goal of this issue with less effort.
This flow was never tested, it may be connected to yield/resume logic or how we assign signature request id.
@ppca can you please take a look?

I will add this issue to our board for better visibility.

@volovyks volovyks moved this from Backlog to Selected in Emerging Technologies Oct 15, 2024
@ppca
Copy link
Contributor

ppca commented Oct 15, 2024

will take a look

@ppca ppca self-assigned this Oct 15, 2024
@ppca
Copy link
Contributor

ppca commented Oct 15, 2024

@mattlockyer if you open the enhanced plan of that transaction: https://testnet.nearblocks.io/txns/6Y3sWjnrytHuTXL3ELkQkdbzbnyeBEQzJpZn2q5B4pAj#enhanced
It says

{
  "type": "action",
  "error": {
    "type": "functionCallError",
    "error": {
      "type": "executionError",
      "error": "Exceeded the prepaid gas."
    }
  }

Can you try attaching more gas to it?

@mattlockyer
Copy link

@mattlockyer are you making a batch transaction with 3 function_call sign actions?

Yes I am, thanks for clarifying. I would be interested in this flow being adopted, since it would open a lot of use cases for handling multiple signatures. Thanks!

@volovyks
Copy link
Collaborator Author

@mattlockyer does it work with increased gas?

@mattlockyer
Copy link

@mattlockyer does it work with increased gas?

The transaction is eventually successful with gas at 95 Tgas per action (smaller gas amounts like 50 and 75 Tgas failed).

But the issue is only 1 signature is returned to the client.

@mattlockyer
Copy link

@mattlockyer if you open the enhanced plan of that transaction: https://testnet.nearblocks.io/txns/6Y3sWjnrytHuTXL3ELkQkdbzbnyeBEQzJpZn2q5B4pAj#enhanced It says

{
  "type": "action",
  "error": {
    "type": "functionCallError",
    "error": {
      "type": "executionError",
      "error": "Exceeded the prepaid gas."
    }
  }

Can you try attaching more gas to it?

Yes I see... This is unfortunate as I attached 95 Tgas per action.

@ppca
Copy link
Contributor

ppca commented Oct 16, 2024

@mattlockyer May I know how you sent the transaction? Any script or commands? So i could reproduce and look into it

@mattlockyer
Copy link

This is a hacky implementation of a batch action call to MPC sign with 3 actions, each slightly modifies the payload. Currently does not work with MPC contract:

// finalArgs are just your args to sign call
const finalArgs = args;
const actions = [];
for (let i = 0; i < 3; i++) {
    // DEBUGGING copy args and modify payload slightly
    const args = JSON.parse(JSON.stringify(finalArgs));
    if (i > 0) {
        args.request.payload.pop();
        args.request.payload.push(i);
    }
// import functionCall from nearApi.transactions.functionCall
    actions.push(
        functionCall(
            'sign',
            args,
            new BN('95000000000000'),
            new BN(attachedDeposit),
        ),
    );
}

let res: nearAPI.providers.FinalExecutionOutcome;
try {
    // receiverId is the NEAR MPC CONTRACT
    // account is a nearApi Account instance
    res = await account.signAndSendTransaction({
        receiverId: contractId,
        actions,
    });
} catch (e) {
    throw new Error(`error signing ${JSON.stringify(e)}`);
}

console.log('NEAR RESPONSE', res);

@mattlockyer
Copy link

Then things get weird. clear_state_on_finish and return_signature_on_finish are only called once. The client only receives 1 status and SuccessValue for the transaction. Despite having 3 functionCall actions attached.

I was mistaken: clear_state_on_finish and return_signature_on_finish were called but exceeded the prepaid gas.

They both had their promise yields timeout at 201 blocks after sign_helper was called at block,
176,903,682. Both timeout in the same block.

Despite the other 2 calls to sign failing for exceeding gas. The client still only received 1 signature response in the final transaction SuccessValue.

@EdsonAlcala and myself looked at the code and wondered the following:

In the transaction: https://testnet.nearblocks.io/txns/6Y3sWjnrytHuTXL3ELkQkdbzbnyeBEQzJpZn2q5B4pAj#execution

sign is called 3 times in the same block.

sign_helper is called 3 times, 2 blocks later, but all calls fall in the same block.

One of the responses is successful, and at block 176,903,489 clear_state_on_finish is called.

The other 2 calls to sign_helper that were yielded (in the same block as the successful one) time out exactly 201 blocks after sign_helper is called, block 176,903,682.

Question:

When the 3 yield promises are created, the DATA_ID_REGISTER is being written to and read back in this same block.

Is it possible that the yield promise data ID being written to and read from the register is returning the same value for all 3 calls?

This could manifest into a number of different runtime errors, but would essentially leave the other 2 calls to sign_helper yielded but never resumed (created but missing a reference?).

We're unclear on how the yield promise resume logic works, but found this portion of code to be troublesome if executed in the same block, same transaction, same predecessor.

let yield_promise = env::promise_yield_create(
"clear_state_on_finish",
&serde_json::to_vec(&(&contract_signature_request,)).unwrap(),
CLEAR_STATE_ON_FINISH_CALL_GAS,
GasWeight(0),
DATA_ID_REGISTER,
);
// Store the request in the contract's local state
let data_id: CryptoHash = env::read_register(DATA_ID_REGISTER)
.expect("read_register failed")
.try_into()
.expect("conversion to CryptoHash failed");
mpc_contract.add_request(&contract_signature_request.request, data_id);

Another, possibly related issue of multiple async calls to sign_helper:

#901

@ppca
Copy link
Contributor

ppca commented Oct 21, 2024

Checking with near protocol on the workings of yield resume

@ppca ppca moved this from Selected to In Progress in Emerging Technologies Oct 21, 2024
@ppca
Copy link
Contributor

ppca commented Oct 22, 2024

Seems like there is nothing special for multiple yield resume in the same block. What happens when you tried attaching more gas? @mattlockyer

@mattlockyer
Copy link

I was only ever able to get the 1/3 signature result in the transaction https://testnet.nearblocks.io/txns/6Y3sWjnrytHuTXL3ELkQkdbzbnyeBEQzJpZn2q5B4pAj#execution

I used 95 Tgas per function call.

@ppca
Copy link
Contributor

ppca commented Oct 22, 2024

I will reproduce it on my end and I'll check our backend and see. Will let you know

@ppca
Copy link
Contributor

ppca commented Oct 22, 2024

I used near rust cli to send a batch transaction of 3 signs:

    /Users/xiangyiz/workspace/near/near-cli-rs/target/release/near transaction construct-transaction v5.multichain-mpc-dev.testnet v1.signer-prod.testnet add-action function-call sign json-args '{"request": {"payload":[12, 1, 2, 0, 4, 5, 6, 8, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 44], "path": "test", "key_version": 0}}' prepaid-gas '95.0 Tgas' attached-deposit '1 NEAR' add-action function-call sign json-args '{"request": {"payload":[14, 1, 2, 0, 4, 5, 6, 8, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 44], "path": "test", "key_version": 0}}' prepaid-gas '95.0 Tgas' attached-deposit '1 NEAR' add-action function-call sign json-args '{"request": {"payload":[15, 1, 2, 0, 4, 5, 6, 8, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 44], "path": "test", "key_version": 0}}' prepaid-gas '95.0 Tgas' attached-deposit '1 NEAR' skip network-config testnet sign-with-keychain send

I'm getting https://testnet.nearblocks.io/txns/EioG9uiYEe64eyMYR8KcVmcvP7TVGWNTZWHiMAANbZkc#enhanced
one of the signs return signature, while the other 2 failed with request has timed out.

I have figured out why they fail with time out, and working on a fix.

I am not able to reproduce the error you had exceeded prepaid gas. @mattlockyer

@ppca
Copy link
Contributor

ppca commented Oct 24, 2024

Hey I'm mostly done with the implementation just trying to make an integration test of this use case, which is taking a bit of time. ETA to dev network is next Monday or Tuesday.

@ppca ppca linked a pull request Oct 25, 2024 that will close this issue
@ppca
Copy link
Contributor

ppca commented Oct 28, 2024

PR in review. Since our team is distributed, might need to take another 2-3 days.

@ppca ppca closed this as completed in #909 Oct 29, 2024
@github-project-automation github-project-automation bot moved this from In Progress to Done in Emerging Technologies Oct 29, 2024
@ppca ppca reopened this Oct 29, 2024
@github-project-automation github-project-automation bot moved this from Done to In Progress in Emerging Technologies Oct 29, 2024
@ppca
Copy link
Contributor

ppca commented Oct 29, 2024

Hey @mattlockyer , the fix is merged and I just tried a batch tx on dev contract: https://testnet.nearblocks.io/txns/B2vuBu1afDRj1ouVnpPYCGYQd9smSH9a9nc6U68bhcxh#
Each sign() has its signature returned, and you could match the signature result with the payloads.
You can use near-cli to send the same tx:
near/near-cli-rs/target/release/near transaction construct-transaction v5.multichain-mpc-dev.testnet v1.signer-dev.testnet add-action function-call sign json-args '{"request": {"payload":[12, 1, 2, 0, 4, 5, 6, 8, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 44], "path": "test_0", "key_version": 0}}' prepaid-gas '95.0 Tgas' attached-deposit '1 NEAR' add-action function-call sign json-args '{"request": {"payload":[14, 1, 2, 0, 4, 5, 6, 8, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 44], "path": "test_1", "key_version": 0}}' prepaid-gas '95.0 Tgas' attached-deposit '1 NEAR' add-action function-call sign json-args '{"request": {"payload":[15, 1, 2, 0, 4, 5, 6, 8, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 44], "path": "test_2", "key_version": 0}}' prepaid-gas '95.0 Tgas' attached-deposit '1 NEAR' skip network-config testnet sign-with-keychain send
I constantly get Error: 0: JSON RPC server responded with an unexpected status code: 408 Request Timeout when I issue the batch request from near cli, although such request is success from near blocks.
We don't have any customization around showing result for a batch tx with multiple sign(), so you might need to pick out the result from the transaction outcome. In rust, only one signature will end up in transaction_outcome, and other signatures will show up in the receipt_outcomes for the batch tx I tested.

@ppca ppca moved this from In Progress to Done in Emerging Technologies Oct 30, 2024
@ppca ppca closed this as completed Oct 30, 2024
@bh2smith
Copy link

bh2smith commented Nov 6, 2024

Excited to see this go live. Ready to use it!

@bh2smith
Copy link

bh2smith commented Nov 6, 2024

I am looking through #909 and I can't see that the following interface was added to the contract:

pub fn sign(&mut self, requests: Vec<SignRequest>) -> Result<near_sdk::Promise, Error>

Is this part of the plan or can we expect that sending multiple Actions as functionCalls to "sign" method will become the standard approach?

@volovyks
Copy link
Collaborator Author

volovyks commented Nov 6, 2024

@bh2smith for now, we will only support batched sign calls if you want to get multiple signatures at once

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Emerging Tech Emerging Tech flying formation at Pagoda Near BOS NEAR BOS team at Pagoda
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

4 participants