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

Attestation pallet #1003

Merged
merged 43 commits into from
Aug 21, 2024
Merged

Attestation pallet #1003

merged 43 commits into from
Aug 21, 2024

Conversation

ameba23
Copy link
Contributor

@ameba23 ameba23 commented Aug 13, 2024

This is part of #982

This adds a pallet for TDX attestation.

The pallet stores the nonces of all pending (requested) attestations, storing them under associated TSS account ID. So there may be at most one pending attestation per TS server. The nonce is just a random 32 bytes, which is included in the input data to the TDX quote, to prove that this is a freshly made quote.

An attestation request is responded to by submitting the quote using the attest extrinsic. If there was a pending attestation for the caller, the quote is verified. Verification currently just means checking that the quote parses correctly and has a valid signature. But this would eventually also check the build-time or run-time measurement details match our current release and that the public key matches the corresponding PCK certificate. PCK certificates will be handled in a separate PR.

If the quote fails to verify, something should happen to the validator - eg: remove them from the signing committee or block them from joining. This is outside of the scope of this PR and can be handled as part of the slashing feature.

The attestation pallet also stores a mapping of block number to TSS account IDs of nodes for who an attestation request should be initiated. This is used by the propagation pallet to make a POST request to the TS server's /attest endpoint whenever there are requests to be made. Currently, the only place these attestation requests are initiated is in the genesis config for testing.

@ameba23 ameba23 marked this pull request as draft August 13, 2024 09:09
ameba23 and others added 28 commits August 13, 2024 11:50
* master:
  TSS attestation endpoint (#1001)
* master:
  Bump serde_json from 1.0.124 to 1.0.125 in the patch-dependencies group (#1007)
  Signing flow with derived accounts (#990)
  Add `network-jumpstart` command to `entropy-test-cli` (#1004)
  Refactor reshare (#994)
  Migrate circle-ci workflow to github actions (#991)
  Delete old keyshare if not in next_signers (#999)
@@ -105,14 +114,14 @@ pub type EncodedVerifyingKey = [u8; VERIFICATION_KEY_LENGTH as usize];
pub struct QuoteInputData(pub [u8; 64]);

impl QuoteInputData {
pub fn new(
tss_account_id: [u8; 32],
pub fn new<T: Encode>(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is just so it can be used with mock account ids in the attestation pallet test.


/// HTTP POST endpoint to initiate a TDX attestation.
/// Not yet implemented.
#[cfg(not(any(test, feature = "unsafe")))]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Before we had a production and mock version of the endpoint function - but that is now replaced with a common endpoint function which internally calls either a mock or production version of the create_quote function

entropy::storage().attestation().pending_attestations(&TSS_ACCOUNTS[0]);
if query_chain(&api, &rpc, pending_attestation_query, None).await.unwrap().is_none() {
return;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test isn't ideal - but currently the only outcome of a successful attestation is that there is no longer a pending attestation.

@ameba23 ameba23 marked this pull request as ready for review August 20, 2024 12:04
Copy link
Member

@JesseAbram JesseAbram left a comment

Choose a reason for hiding this comment

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

Looks good a few issues need to be made

use crate::{chain_api::get_rpc, get_signer_and_x25519_secret};
use rand_core::OsRng;
use sp_core::Pair;
) -> Result<StatusCode, AttestationErr> {
Copy link
Member

Choose a reason for hiding this comment

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

we will have to validate this message agains blockchain data (can be in a later PR but needs an issue

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll put it in the issue - i am adding a bunch of things into the once issue for this feature.

But since we check below with the chain that there is a pending attestation, im not sure what we gain by also checking the OcwMessageAttestationRequest matches on chain.

entropy::storage().attestation().pending_attestations(signer.account_id());
query_chain(&api, &rpc, pending_attestation_query, None)
.await?
.ok_or_else(|| AttestationErr::Unexpected)?
Copy link
Member

Choose a reason for hiding this comment

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

wouldn't hate a better error message here, a generalize one for querying chains maybe that takes a string of a better description

/// attestation, used to make attestation requests via an offchain worker
#[pallet::storage]
#[pallet::getter(fn attestation_requests)]
pub type AttestationRequests<T: Config> =
Copy link
Member

Choose a reason for hiding this comment

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

idea here, we can make a trailing request of X blocks on an on init hook that boots a validator if they did not complete their attestation

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added this to the TODOs in the issue

);

// Remove the entry from PendingAttestations
PendingAttestations::<T>::remove(&who);
Copy link
Member

Choose a reason for hiding this comment

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

this should be below the other two probably

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've moved it lower down for now but Im not sure about this.

I think once we have some specific outcome for a failed attestation, we will still want to remove the pending attestation even if it is bad. So pending attestations will just be ones that haven't been responded to yet and we will have some other way of representing ones which have been responded to but were bad.


// Remove the entry from PendingAttestations
PendingAttestations::<T>::remove(&who);

Copy link
Member

Choose a reason for hiding this comment

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

Idea: we put in the validator info when their last successful attestation was made to chain

@HCastano HCastano added the Bump `spec_version` A change which affects the current runtime behaviour label Aug 20, 2024
Copy link
Collaborator

@HCastano HCastano left a comment

Choose a reason for hiding this comment

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

Left some comments and questions, but nothing major. Good work 👍

Make sure to add a CHANGELOG entry before merging

// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! # Attestation Pallet
Copy link
Collaborator

Choose a reason for hiding this comment

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

Some docs about what this is would be nice

#[derive(frame_support::DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
pub initial_pending_attestations: Vec<(T::AccountId, [u8; 32])>,
pub initial_attestation_requests: Vec<(BlockNumberFor<T>, Vec<Vec<u8>>)>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
pub initial_attestation_requests: Vec<(BlockNumberFor<T>, Vec<Vec<u8>>)>,
pub initial_attestation_requests: Vec<(BlockNumberFor<T>, Vec<T::AccountId>)>,

Why not do something like this instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. I thought it is not possible to have a <T::AccountId> as a value in a StorageMap. As in all our other pallets, we use Vec<u8>. But actually i just tried it, and it compiled fine.

So now the issue is that in entropy_shared::OcwMessageAttestationRequest we represent this as a [u8; 32]. Since theres no way to turn a generic T::AccountId into a [u8; 32], we would have to make OcwMessageAttestationRequest generic for chain config.

I'd be happy to do this, but i have the feeling it there might be issues with deriving Serialize and Deserialize and probably it would make sense to also do it to all the other types we have in entropy-shared which represent account ids with Vec<u8>. So maybe its for another PR.

#[pallet::storage]
#[pallet::getter(fn pending_attestations)]
pub type PendingAttestations<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, [u8; 32], OptionQuery>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

We could typedef this as type Nonce = [u8; 32]

Comment on lines 94 to 98
BadQuote,
UnexpectedAttestation,
IncorrectInputData,
NoStashAccount,
NoServerInfo,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I know we don't do it for the other pallets, but let's try and add doc comments before it's too late lol

impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight({100})]
pub fn attest(origin: OriginFor<T>, quote: Vec<u8>) -> DispatchResult {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Missing doc comments. Would be helpful to know what a quote should look like for example

Comment on lines +70 to +72
assert_last_event::<T>(
Event::<T>::AttestationMade.into()
);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here we can also test for expected behaviour.

For example, we can check that PendingAttestations doesn't have the validator ID anymore, or that expected events were emitted

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea. I've added the check that there is no longer a pending attestation. Not sure what other events i could check for.


let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(2_000));
let kind = sp_core::offchain::StorageKind::PERSISTENT;
let from_local = sp_io::offchain::local_storage_get(kind, b"attest")
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we also need to reflect this in the service

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah ok yep will do.

Although i find it strange that we store a bunch of these urls for each endpoint, rather than just a top level one.

@@ -336,5 +336,9 @@ pub fn development_genesis_config(
10,
)],
},
"attestation": AttestationConfig {
initial_attestation_requests: vec![(3, vec![crate::chain_spec::tss_account_id::ALICE.to_raw_vec()])],
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do you mind using an account instead of 3 here? Either a typedef or the result from one of the other helpful functions is fine

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a block number, not an account id

crates/threshold-signature-server/src/attestation/api.rs Outdated Show resolved Hide resolved
let api = get_api(&cxt.ws_url).await.unwrap();
let rpc = get_rpc(&cxt.ws_url).await.unwrap();

// Check that there is an attestation request at block 3 from the genesis config
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why block 3?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was one of those problems which kind of got sorted but i don't completely understand why.

The propagation pallet's off chain worker hooks which run every block don't run on the genesis block. It seems to me that they also don't run on block one - although me and Jesse were getting different behavior. In the end i've ended up with block 3, and also am not subtracting 1 from the block number. Probably this could be either changed to 2, or the subtract 1 could be put back in.

@ameba23 ameba23 merged commit 305c4be into master Aug 21, 2024
8 checks passed
@ameba23 ameba23 deleted the peg/attestation-pallet branch August 21, 2024 10:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bump `spec_version` A change which affects the current runtime behaviour
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants