Repository navigation
mtpublisher: Store the mirror's checkpoint line and subtree signatures - #9097
beautifulentropy wants to merge 5 commits into
Conversation
|
@beautifulentropy, this PR appears to contain configuration and/or SQL schema changes. Please ensure that a corresponding deployment ticket has been filed with the new values. |
jsha
left a comment
There was a problem hiding this comment.
Looking great, glad to see this making its way out. I think the most important decision to make here is related to batching of work and how we recover from getting behind. I talk about it in line comments below, but overall I think: small batches, short intervals, last-in-first-out (i.e. stack semantics), and mirrors should continue to get entries pushed regardless of whether subtrees are getting signed successfully.
| // with origin. | ||
| // | ||
| // https://c2sp.org/tlog-cosignature#subtree-cosignatures | ||
| func (v *Verifier) VerifySubtree(origin string, start, end int64, subtreeHash tlog.Hash, signature []byte) bool { |
There was a problem hiding this comment.
| func (v *Verifier) VerifySubtree(origin string, start, end int64, subtreeHash tlog.Hash, signature []byte) bool { | |
| func (v *Verifier) VerifySubtreeSignature(origin string, start, end int64, subtreeHash tlog.Hash, signature []byte) bool { |
To emphasize that we're verifying a signature, not an inclusion proof.
| -- mtpublisher: follows the latestCheckpoint pointer to a checkpoint awaiting | ||
| -- a cosignature and writes the mirror's cosignatures on it and its subtrees. |
There was a problem hiding this comment.
Probably best to just remove this comment altogether; we're likely to miss updating it as code elsewhere changes, and I think we're unlikely to remember to go and update the user grants SQL.
| return model.ID, nil | ||
| } | ||
|
|
||
| // GetUnsignedSubtrees returns the log's subtrees without a mirror signature, oldest first. |
There was a problem hiding this comment.
I think we want this to be newest-first, with a LIMIT just in case the list gets really big.
Reasoning: as written, this acts kind like a queue. In #6042 we decided stack-like behavior fails nicer. If we get overwhelmed, we handled the freshest stuff first since older stuff may already be forgotten by the client that wanted it.
In the happy case, there will only be a tiny handful of subtrees with mirrorID IS NULL so it won't matter, but it's good to fail gracefully when we fail.
| // log's latest checkpoint. | ||
| // ErrSubtreeNotMirrored reports that the mirror's checkpoint does not include | ||
| // the subtree yet. | ||
| var ErrSubtreeNotMirrored = errors.New("the mirror has not reached the subtree") |
There was a problem hiding this comment.
| var ErrSubtreeNotMirrored = errors.New("the mirror has not reached the subtree") | |
| var ErrSubtreeNotMirrored = errors.New("the mirror's latest checkpoint does not include the subtree") |
|
|
||
| // Start attempts to publish the latest checkpoint at each interval until ctx is | ||
| // CosignSubtrees stores the mirror's signature on each subtree of the log that | ||
| // lacks one, oldest first, until it reaches one the mirror's checkpoint does |
There was a problem hiding this comment.
| // lacks one, oldest first, until it reaches one the mirror's checkpoint does | |
| // lacks one, newest first, until it reaches one the mirror's checkpoint does |
(if you take my suggestion below about selecting from the DB newest-first)
| err = subtree.Valid() | ||
| if err != nil { | ||
| return fmt.Errorf("validating subtree %d: %s", subtree.ID, err) | ||
| } |
There was a problem hiding this comment.
If we somehow write an invalid subtree to the DB, the mtpublisher will forever get stuck on it. Given that such a subtree can't possibly ever succeed in getting a signature, I think this case should intentionally write an invalid mirrorID to the table so we don't keep trying.
| err := p.PublishCheckpoint(ctx) | ||
| if err != nil { | ||
| p.log.Errf("Publishing checkpoint failed: %s", err) | ||
| } | ||
| err = p.CosignSubtrees(ctx) |
There was a problem hiding this comment.
I think it makes sense for PublishCheckpoint and CosignSubtrees to have independent loops, in separate goroutines (with wg.Do() so this function can block on them finishing).
Reasoning: if for any reason CosignSubtrees is slow or gets stuck in a loop, we don't want that to get in the way of publishing our latest tree state.
Relatedly: we should decide how much work CosignSubtrees does per call. Or another way to put it: how big a batch of work does it get from GetUnsignedSubtrees? I think the answer should be "very small, like 2 items, and the interval should be quite small." If we chose big batches instead, the information from GetUnsignedSubtrees would be stale when we're working on the tail end of the batch. Specifically, there could already be a mirror signature available for a given subtree but this mtpublisher instance wouldn't necessarily know that.
| separator := bytes.Index(body, []byte("\n\n")) | ||
| if separator < 0 { | ||
| return nil, nil, errors.New("mirror checkpoint has no signature lines") | ||
| } | ||
| zeroTimestampMirrorCosignature, err := m.verifier.FilterByVerify(noteText, subtreeCosignatureLines) | ||
| noteText := body[:separator+1] | ||
| signatureLines := body[separator+2:] |
There was a problem hiding this comment.
I think we can replace this parsing code with checkpoint.Open(). If that's not possible we should have a comment explaining.
| if mirrored.Tree.N == end && mirrored.Tree.Hash != subtreeHash { | ||
| return nil, fmt.Errorf("mirror checkpoint at size %d has root hash %x, the log's is %x", end, mirrored.Tree.Hash, subtreeHash) | ||
| } |
There was a problem hiding this comment.
I think we should replace this check with a call to subtree.VerifyConsistency immediately after subtree.ConsistencyProof. That verifies the same property, but for all cases instead of the special case that the subtree lines up with the current tree size.
.Publish()into.PublishCheckpoint(), storing the mirror's checkpoint cosignature line fromadd-entries, and.CosignSubtrees(), storing the mirror's signature on each subtree that lacks one, both are run at each tick.sign-subtree..CosignSubtree()toTestMirror.monitoringPrefixconfiguration.Cosigner.CosignSubtree()andVerifier.VerifySubtree()over any subtree, and return theLine,Timestamp, andSignatureof a checkpoint cosignature fromFilterByVerify()as aCheckpointCosignaturestruct.Cosigner.CosignSubtree()..GetUnsignedSubtrees()and.AddSubtreeMirrorSignature(), and dropsubtreeID1andsubtreeID2from thecheckpointstable.checkpointSubtreesbymtcLogIDandmirrorID, and grant themtpublisheruserUPDATE.Note: tlog-witness v1.1.0 was updated to respond with a bare (base64 encoded) subtree cosignature. Sunlight v0.9.0 responds to sign-subtree requests with a note signature line. We'll update our client to expect that once Sunlight ships an update.
Closes #9037
Closes #8991