Skip to content

Conversation

@laanwj
Copy link
Member

@laanwj laanwj commented Feb 17, 2016

Add a RPC call to verify a set of raw transactions without propagating them. It has an extensible API to customize verification options.

Implements #4162.

verifyrawtransactions ["hexstring",...] ( options )

Verifies one or more raw transactions (serialized, hex-encoded). If transactions depend on each other, they must be provided in order.

Arguments:
1. ["hexstring",...] (array of strings, required) The hex string of the raw transactions)
2. options   (json object, optional)
     {
       "include_mempool"          (boolean, optional, default=true) Whether to include the mem pool
       "check_final"              (boolean, optional, default=true) Check that the transactions will be final by next block
       "check_standard"           (boolean, optional, default=true) Perform transaction standard checks
     }

Result:
null if the verification was successful, otherwise an error object:
{
  "index":n,                (numeric) Index in transactions array of failed transaction
  "hash":"hex",             (string) Transaction hash of failed transaction
  "code": n,                (numeric) Reject code
  "reason": "text"          (string) Reject reason
  "debug_message": "text"   (string) Reject debug message
}

TODO:

  • RPC test
  • Call AreInputsStandard() to check inputs for standardness

@jonasschnelli
Copy link
Contributor

Concept ACK.

@dcousens
Copy link
Contributor

Concept ACK

@gmaxwell
Copy link
Contributor

Concept-ACK.

Should the sense of final and non-standard be inverted in the options? It would be surprising to pass a transaction to this RPC, have it pass.... then find that the network won't accept it. Practically speaking, except for special cases and developers standardness is pretty much as forceful as validity.

An obvious future extension wrt finality would be adding a field in the result that says when the transaction will become final.

@dcousens
Copy link
Contributor

@gmaxwell would it not be easier to make an optional parameter the estimated block height?
This would probably need to be specified for each transaction if we're passing a list (so you can do CSV chains etc).
Or would it be better to just give that feedback to the user as the "earliest possible block height" it could be included in? [per transaction]

edit: As suggested by @gmaxwell (I think), IMHO the best approach here is to give feedback to the user as to the earliest time they could be included.

@laanwj
Copy link
Member Author

laanwj commented Feb 19, 2016

it would be surprising to pass a transaction to this RPC, have it pass.... then find that the network won't accept it.

That's why those checks are enabled (true) by default. For specific cases they could be disabled. Only for the coinbase check I couldn't think of any reason why one'd want to disable it, so I left that out.

An obvious future extension wrt finality would be adding a field in the result that says when the transaction will become final.

Yes, would be nice - by using an object both for the options and the error, I made the API extensible enough to add such bells and whistles.
(either a "pass a simulated height", or "report when final" approach, or both, could fit in)

@laanwj laanwj force-pushed the 2016_02_verifytransaction branch from 2845dc1 to f757579 Compare February 19, 2016 09:19
@laanwj laanwj mentioned this pull request Feb 19, 2016
@instagibbs
Copy link
Member

concept tested ACK, this is great for testing smart contract stuff with timelocks.

@jtimon
Copy link
Contributor

jtimon commented Feb 20, 2016

concept ack, but this is very incomplete, I suggest we keep it open without merging too fast.

We're arbitrarily calling IsStandardTx() but not AreInputsStandard(). Those are just two easily functions containing relay/ming policy, but there's much more related code spread out there.

Nit: Remove call to IsStandardTx() in verifytx, we can make another rpc call (or add an option in this one) called "verify tx according to consensus rules and also accept tx according to some confirgurable policy parameter". But please let's not add any policy code until we have a complete Consensus::VerifyTx().
Wait a minute, Consensus::VerifyTx() doesn't even exist on master yet, not in a complete form and neither in an incomplete form.

Nit: Let's please introduce Consensus::VerifyTx() before exposing anything else, otherwise you'll have to duplicate some code from AcceptToMemoryPool and/or ConnectBlock. Could you please consider rebasing this PR on top of #7565 ?

@dcousens
Copy link
Contributor

IMHO agreed this would be best implemented by Consensus::VerifyTx or the like.

I also like the idea of a parameter for rejection based purely on consensus, and irrespective of local node policy.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Appears to be missing the left parenthesis.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or adding a superfluous right one

@laanwj
Copy link
Member Author

laanwj commented Mar 3, 2016

@jtimon I don't feel like doing all kinds of refactors here (too much risk to break other things). Please just review this for correctness.

@laanwj
Copy link
Member Author

laanwj commented Mar 3, 2016

I also like the idea of a parameter for rejection based purely on consensus, and irrespective of local node policy.

Right - that's possible already. Disable check_standard, and possible check_final, or am I missing something?

@jtimon
Copy link
Contributor

jtimon commented Mar 3, 2016

Fair enough, concept ack.
I would still prefer a more generic and extensible option than bool check_standard (string policy="default"/"none" ?).
I still don't know why isstandard is called but areinputsstandard isn't. There's more to-be-consensus code out of here (that's what I mean by this is incomplete ).

@laanwj
Copy link
Member Author

laanwj commented Mar 3, 2016

I still don't know why isstandard is called but areinputsstandard isn't.

Yes, that's a legit bug (I added it to TODOs in the opening post)

I would still prefer a more generic and extensible option than bool check_standard (string policy="default"/"none" ?).

What other options would you imagine there? Adding more options to the options structure is free, so there could always be a "standard_policy": "string" added later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote some code to test the ordering of dependent tx's, then realized this code here does the same thing my code did. If you want to also test calling verifyrawtransaxtions with dependent tx's, you could add this code at this point. It was suggested to me to copy-paste the git diff result:

diff --git a/qa/rpc-tests/rawtransactions.py b/qa/rpc-tests/rawtransactions.py
index 9039bc6..9dd8c84 100755
--- a/qa/rpc-tests/rawtransactions.py
+++ b/qa/rpc-tests/rawtransactions.py
@@ -226,6 +226,19 @@ class RawTransactionsTest(BitcoinTestFramework):
         assert(err['code'] == 16)
         assert(err['reason'].startswith('bad-txns-inputs-missingorspent'))

+        #check ordering of dependent tx's in verifyrawtransactions
+        parent  = rawTxSigned['hex']
+        child   = rawTx2
+        err1     = self.nodes[2].verifyrawtransactions([parent, child], {'include_mempool':True})
+        err2     = self.nodes[2].verifyrawtransactions([parent, child], {'include_mempool':False})
+        assert(err1 is None and err2 is None)
+
+        # out-of-order succeeds with mempool, fails without
+        err1     = self.nodes[2].verifyrawtransactions([child, parent], {'include_mempool':True})
+        err2     = self.nodes[2].verifyrawtransactions([child, parent], {'include_mempool':False})
+        assert(err1 is None and err2 is not None)
+        assert(err2['reason'].startswith('bad-txns-inputs-missingorspent'))
+
         # mine the transaction into a block and check end result
         rawTx = self.nodes[0].decoderawtransaction(rawTxSigned['hex'])
         self.sync_all()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, will add that test!

@laanwj laanwj force-pushed the 2016_02_verifytransaction branch from 48a481a to 0ae331b Compare March 14, 2016 14:18
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assertion failed: 

  File "/home/travis/build/bitcoin/bitcoin/bitcoin-x86_64-unknown-linux-gnu/qa/rpc-tests/test_framework/test_framework.py", line 135, in main

    self.run_test()

  File "/home/travis/build/bitcoin/bitcoin/bitcoin-x86_64-unknown-linux-gnu/qa/rpc-tests/rawtransactions.py", line 64, in run_test

    assert(err['code'] == 16)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, interesting, so CheckSequenceLocks actually looks up the inputs, moving it after HaveInputs
(more consistent with AcceptToMemoryPool anyhow)

laanwj added 5 commits March 18, 2016 14:35
Add a RPC call to verify a set of raw transactions without propagating them.

Implements bitcoin#4162.

    verifyrawtransactions ["hexstring",...] ( options )

    Verifies one or more raw transactions (serialized, hex-encoded). If transactions depend on each other, they must be provided in order.

    Arguments:
    1. ["hexstring",...] (array of strings, required) The hex string of the raw transactions)
    2. options   (json object, optional)
         {
           "include_mempool"          (boolean, optional, default=true) Whether to include the mem pool
           "check_final"              (boolean, optional, default=true) Check that the transactions will be final by next block
           "check_standard"           (boolean, optional, default=true) Perform transaction standard checks
         }

    Result:
    null if the verification was successful, otherwise an error object:
    {
      "index":n,                (numeric) Index in transactions array of failed transaction
      "hash":"hex",             (string) Transaction hash of failed transaction
      "code": n,                (numeric) Reject code
      "reason": "text"          (string) Reject reason
      "debug_message": "text"   (string) Reject debug message
    }
For future extensibility it's important that unrecognized options are rejected.
@laanwj laanwj force-pushed the 2016_02_verifytransaction branch from 3101397 to da0595d Compare March 18, 2016 13:52
@laanwj
Copy link
Member Author

laanwj commented Mar 18, 2016

I've factored out the transaction verification to a (local) VerifyTransaction function.

laanwj added 2 commits March 22, 2016 09:17
@laanwj laanwj closed this Mar 28, 2016
@laanwj
Copy link
Member Author

laanwj commented Mar 28, 2016

Too much disagreement on how this should be implemented, not going to bother anymore.

@jtimon
Copy link
Contributor

jtimon commented Mar 28, 2016

I don’t understand why this is closed. I asked to wait for some refactors first. You said no, fair enough, they can be done later. Then you create the verifyTx function, but not in the consensus package or dir, and including policy code. I can't say I like that (I'm not going to link to the VerifyTx I like anymore because as said it requieres refactors and I was waiting for 0.12.1 first. I guess I wouldn't mind if the function was called VerifyAndAcceptTx or something like that.

But why not continue with your original version? Wasn't I the only person complaining and didn't I said fair enough and concept ACK?
I really can't see "too much disagreement here, unless you're talking about some conversation on IRC or somewhere else. If that's the case, a link to the log of the conversation would be welcomed here for traceability.

@laanwj
Copy link
Member Author

laanwj commented Mar 28, 2016

But why not continue with your original version? Wasn't I the only person complaining and didn't I said fair enough and concept ACK?
I really can't see "too much disagreement here, unless you're talking about some conversation on IRC

Well someone else on IRC was worried that the output of this doesn't exactly match the output of sendrawtransaction.

<runeks> wumpus: Awesome! Looks like just what I need.
<runeks> But, as others in the pull request have mentioned, we better be damn sure that its return result is *always* the same as that of sendrawtransaction.
<runeks> Looking at the code, to me it doesn't seem trivial achieve this.
<runeks> Implementation-wise, it would seem a lot more robust to use the same code path as sendrawtransaction, but abort before the transaction is committed, and just return "valid".
<wumpus> that would be a non-trivial impact to the current transaction handling code
<wumpus> not a good idea with all the softforks etc going on
<wumpus> anyhow, closing it then, feel free to implement it in a way you think is better
<wumpus> I thought this was a good way to get the API merged, it could always be improved later
<wumpus> also the 'verify multiple transactions' part would be more difficult to implement if you want to bolt this onto main.cpp

I don't however want to just bolt this on to main.cpp in the current state. Without refactors that'd make the code even harder to understand, and makes it virtually impossible to handle multiple dependent transactions without actually writing to the mempool.
(and the mempool logic in AcceptToMempool really gets in the way, e.g. the replacment logic)

So if this solution is not acceptable for the mean time, I suppose we should wait for those refactors to materialize first.

@jtimon
Copy link
Contributor

jtimon commented Mar 28, 2016

Thank you.

Why the additional requirement of this having to return the same as sendrawtx?

Wouldn't everything be much easier if VerifyTx ONLY did consensus checks? As said we can have a VerifyAndAcceptTx later thatalso does relay/mempool policy stuff as well (but, yeah, I agree the policy code needs many refactors if you want to use any of the fee-related code in the CtxMempool and AcceptToMemoryPool from here).

@laanwj
Copy link
Member Author

laanwj commented Mar 28, 2016

Why the additional requirement of this having to return the same as sendrawtx?

That is the default assumption. I suppose if we can document the differences (e.g. no mempool replacement, no fee checks) that'd help.

If you want to do only consensus checks you can already do that with this code, just disable the standardness check (and possibly the use mempool flag). I do think checking standardness etc is useful in some cases though.

@jtimon
Copy link
Contributor

jtimon commented Mar 29, 2016

Well, yeah, documenting the differences seems much simpler to make the relay/mempool policy checks complete. So called "standardness" is encapsulated in policy/policy.o and its dependencies, but completing the rest of the policy checks won't be so simple I think.
I would rather complete consensus or to-be-consensus checks first (ie bips 68, 112 and 113).
With or without "standardness", I may backport this code to https://github.com/jtimon/bitcoin/tree/jt (after I rebase it on top of bip9, etc) to see if it can help me make some point about encapsulation or initiate some API discussion at the rpc level (something that I've failed to achieve at the C API level, with, say, jtimon@11fa11a ).

If anyone else wants to continue this for master directly, I'm happy to re-review, though.

@NicolasDorier
Copy link
Contributor

NicolasDorier commented Jul 15, 2016

Wouldn't the easiest way to make this feature is to create a dummy block, with a dummy coinbase, and add the transaction inside it and call ConnectBlock with fJustCheck to true ?

@jtimon
Copy link
Contributor

jtimon commented Jul 15, 2016

I don't think the goal of this PR was offering the functionality the "easiest way", but since it was checking policy things as well, I'm not sure I ever understood the PR.
In any case, what you propose (which doesn't sound very elegant) would also not be complete: currently you would need to also call ContextualCheckBlock() with that dummy block for the tx to be fully verified.

@NicolasDorier
Copy link
Contributor

At the API level, I think users care only about one parameter: Whether we check against Node Policy or Consensus Policy. I think the current PR was attempting too many options.

I'll probably try to make it later since this PR already did most of the heavy work.

@laanwj
Copy link
Member Author

laanwj commented Jul 18, 2016

Wouldn't the easiest way to make this feature is to create a dummy block, with a dummy coinbase, and add the transaction inside it and call ConnectBlock with fJustCheck to true ?

Originally I meant this as a pre-check whether sendrawtransaction will accept a chain of transactions.
Creating a dummy block would work to check consensus validity only.

I think the current PR was attempting too many options.

Only three options! And I think all of them make sense in some context.

@bitcoin bitcoin locked as resolved and limited conversation to collaborators Sep 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants