-
Notifications
You must be signed in to change notification settings - Fork 976
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
cap28 - remove pre auth signers on signature verification failure #2379
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change looks correct (other than the fact that it doesn't account for txBAD_SEQ
as we discussed), but I feel like it should be possible to do this more efficiently and in a more "obvious" way.
8b4c293
to
a8634fb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think from a PR clarity perspective, it would probably be much better to make the change that simplifies how one-time signers are removed (eg. remove mUsedOneTimeSigners
and move everything into TransactionFrame
) before the protocol change. That would make it possible to review the refactor using today's semantics. Then the actual protocol change should be just a few lines on top of that.
I had a difficult time reviewing because the two changes are kind of blended together.
420b53d
to
45643a2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is getting close: found another bug, and a few nitpicks. This is way easier to review since you re-ordered the commits, but can you re-write the timestamps so GitHub displays them in the right order?
@@ -684,6 +692,12 @@ TransactionFrame::apply(Application& app, AbstractLedgerTxn& ltx, | |||
auto signaturesValid = cv >= (ValidationType::kInvalidPostAuth) && | |||
processSignatures(signatureChecker, ltxTx); | |||
|
|||
if (cv >= ValidationType::kInvalidRemoveOneTimeSigners && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should instead modify processSignatures
to deal with this (it's signature related, after all): this code was hard to follow, and this makes it harder.
return; // probably account was removed due to merge operation | ||
} | ||
|
||
if (removeAccountSigner(header, account, signerKey)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could clean this up too:
- I'd expect
normalizeSigners
to be called fromremoveAccountSigner
(there is no reason to split this as a an account entry with signers unsorted is illegal) - as removing a signer doesn't change the sort order, we don't need to normalize
- basically, we can just remove the call to
normalizeSigners
- we already check that signers are sorted with the
LedgerEntryIsValid
invariant (so no need to add an extraassert
)
removeUsedOneTimeSignerKeys(signatureChecker, ltxOuter); | ||
if (ledgerVersion < 13) | ||
{ | ||
removeOneTimeSignerFromAllSourceAccounts(ltxOuter); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe we could add a bool
property to signatureChecker
(set where we used to add to the map of used one time signatures) to know if one time signers were used at all to decide if we want to call that function?
The new code is always going to loop over all accounts / all signers (so loading up to 101 accounts and perform 101*20=2020
extra checks for no reason)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this makes sense. In the event that we aren't checking signatures (eg. cv < ValidationType::kInvalidPostAuth
) then we have to loop over all accounts and signers, since they haven't been checked by SignatureChecker
. In the event that we are checking signatures (eg. cv >= ValidationType::kInvalidPostAuth
) then we have already loaded the accounts in the SignatureChecker
so they are in memory and looping over the accounts and signers requires no I/O.
In the worst case, we have to do the full loop including I/O. In all other cases, we have already done the I/O so it shouldn't be expensive. If at a future date we assess that this is an unacceptable amount of overhead, let's optimize and make the code more complicated at that point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this was more a comment on the fact that in the previous version we had 0 overhead when not using one time signers to an implementation that bears the overhead all the time (all that for a feature that is virtually never used).
I am quite not sure what the overhead of this is: maintaining LedgerTxn
is not free, we had examples in the past where the overhead was managing maps.
I guess what we can do is perform some performance test comparison of this new implementation to compare it against 12.5.0, replaying protocol 12 ledger ranges with version 13.0.0 both with protocol 12 and 13.
df7a018
to
d2e2bb6
Compare
processSignatures(signatureChecker, ltxTx); | ||
auto signaturesValid = | ||
cv >= (ValidationType::kMaybeValidCheckSignatures) && | ||
processSignatures(signatureChecker, ltxTx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand the name kMaybeValidCheckSignatures
: it's invalid for sure as it failed with txINSUFFICIENT_BALANCE
.
If we want to rename something, I'd rename kFullyValid
that is kMaybeValid
as we didn't check for signatures yet.
Also, per my previous comment, the only reason we're calling processSignature
in this case is to remove one time signers, so it would be a lot clearer to move the call to removeOneTimeSignerFromAllSourceAccounts
into that processSignature
function.
I would change the contract of processSignatures
to return if the transaction is valid.
So this code would instead look like
valid = processSignatures(cv, signatureChecker, ltxTx);
(and we'd remove the valid = signaturesValid && (cv == ValidationType::kFullyValid);
later)
As we make this change, I think it's better to "fast fail" with the proper error code in protocol 13. This way the transaction will fail with txINSUFFICIENT_BALANCE
instead of (potentially) some other error like txBAD_AUTH_EXTRA
.
the actual function then becomes:
bool
TransactionFrame::processSignatures(ValidationType cv, SignatureChecker& signatureChecker,
AbstractLedgerTxn& ltxOuter)
{
auto maybeValid = (cv == ValidationType::kMaybeValid);
auto allOpsValid = true;
uint32_t ledgerVersion;
LedgerTxn ltx(ltxOuter);
ledgerVersion = ltx.loadHeader().current().ledgerVersion;
if (ledgerVersion < 10)
{
return maybeValid;
}
// check if we need to fast fail and use the original error code
if (ledgerVersion >= 13 && !maybeValid)
{
removeOneTimeSignerFromAllSourceAccounts(ltx);
ltx.commit();
return false;
}
// older versions of the protocol only fast fail in a subset of cases
if (ledgerVersion < 13 && cv < ValidationType::kInvalidPostAuth)
{
return false;
}
{
// scope here to avoid potential side effects of loading source accounts
LedgerTxn ltx2(ltx);
for (auto& op : mOperations)
{
if (!op->checkSignature(signatureChecker, ltx2, false))
{
allOpsValid = false;
}
}
}
removeOneTimeSignerFromAllSourceAccounts(ltx);
ltx.commit();
if (!allOpsValid)
{
markResultFailed();
return false;
}
if (!signatureChecker.checkAllSignaturesUsed())
{
getResult().result.code(txBAD_AUTH_EXTRA);
return false;
}
return maybeValid;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I added a comment on the scope around LedgerTxn ltx2(ltx)
, I realized that:
- either this scope is not needed (pretty much sure it is)
- or we have a potential bug with the new
removeOneTimeSignerFromAllSourceAccounts
that loads all source accounts - it should only load & modify the ones that get changed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand the name kMaybeValidCheckSignatures: it's invalid for sure as it failed with txINSUFFICIENT_BALANCE.
As we make this change, I think it's better to "fast fail" with the proper error code in protocol 13. This way the transaction will fail with txINSUFFICIENT_BALANCE instead of (potentially) some other error like txBAD_AUTH_EXTRA.
Yes, you're absolutely right about this. When I wrote that comment I didn't notice that we check the balance then check the signatures regardless of the result of the balance check. In an ideal world, we would never have even checked signatures in this case. Let's make this change in protocol 13. Let's also rename kFullyValid
to kMaybeValid
as you suggest.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also in the code @MonsieurNicolas suggested, I don't believe that LedgerTxn ltx(ltxOuter);
(line 8) is required. However, the scope with LedgerTxn ltx2(ltx);
is required (but let's just call it ltx
now).
You are correct that removeOneTimeSignerFromAllSourceAccounts
is currently wrong, it should only load and modify the ones that get changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I modified removeOneTimeSignerFromAllSourceAccounts
to first look for the signer through loadAccountWithoutRecord
.
Something to scrutinize here is that I use the iterator derived from the loadAccountWithoutRecord
account signers to erase from the loadAccount
account signers to avoid a second search. This should be fine since the LedgerEntry
from the first load should be cached, but I'm wondering if it might be safer and cleaner to just do the second search if we get a match on the signer (which isn't a common scenario). @jonjove
I also added a check in TxTests.cpp
that verifies any delta from an early failure post V13 must have a signature removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't safe. Every time a new LedgerEntry
is loaded it produces a copy. So the vector of signers for the mutable LedgerTxnEntry
will be a copy of your initial vector from the ConstLedgerTxnEntry
; the initial vector may even have been destructed.
That being said, you don't actually need to search again. The signers are sorted which guarantees they won't change order, so you can calculate the index of the relevant signer. Then you can just index into the new vector directly (I would recommend adding an assert here to check that you actually found the right signer).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
5281757
to
bd253bb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks very good now. I had couple of very minor nitpicks, and one slightly more important comment to make this easier/safer to maintain in the future.
1411dc6
to
13687e1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This mostly looks good. The main issue is that it doesn't pass tests (because of an error in the tests, not the implementation). The other three comments are all about making the code easier to maintain and reason about. I spent an unusually long time trying to understand how "merge source account before payment" works, so it is probably worth simplifying and adding comments.
This is ready to merge once you squash. |
r+ 70878d5 |
Description
Implements https://github.com/stellar/stellar-protocol/blob/master/core/cap-0028.md
Checklist
clang-format
v5.0.0 (viamake format
or the Visual Studio extension)