Skip to content
This repository has been archived by the owner on Nov 29, 2024. It is now read-only.

Commit

Permalink
imp: add proof caching for aggregation (#110)
Browse files Browse the repository at this point in the history
* feat: added caching

* imp: limited code to membership and added tests

* imp: improved contract and test

* style: renamed tests

* lint: fixed linter issues

* style: addressed review items
  • Loading branch information
srdtrk authored Oct 8, 2024
1 parent ed80376 commit c5c7aa3
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 13 deletions.
33 changes: 29 additions & 4 deletions contracts/src/SP1ICS07Tendermint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ contract SP1ICS07Tendermint is
ClientState private clientState;
/// @notice The mapping from height to consensus state keccak256 hashes.
mapping(uint32 height => bytes32 hash) private consensusStateHashes;
/// @notice The collection of verified SP1 proofs for caching.
mapping(bytes32 sp1ProofHash => bool isVerified) private verifiedProofs;

/// @notice Allowed clock drift in seconds.
/// @inheritdoc ISP1ICS07Tendermint
Expand Down Expand Up @@ -178,7 +180,6 @@ contract SP1ICS07Tendermint is
bytes calldata kvValue
)
private
view
returns (uint256)
{
if (proofHeight.revisionNumber != clientState.latestHeight.revisionNumber) {
Expand Down Expand Up @@ -222,7 +223,12 @@ contract SP1ICS07Tendermint is

validateMembershipOutput(output.commitmentRoot, proofHeight.revisionHeight, proof.trustedConsensusState);

verifySP1Proof(proof.sp1Proof);
// We avoid the cost of caching for single kv pairs, as reusing the proof is not necessary
if (output.kvPairs.length == 1) {
verifySP1Proof(proof.sp1Proof);
} else {
verifySP1ProofCached(proof.sp1Proof);
}

return proof.trustedConsensusState.timestamp;
}
Expand All @@ -234,6 +240,7 @@ contract SP1ICS07Tendermint is
/// @param kvPath The path of the key-value pair.
/// @param kvValue The value of the key-value pair.
/// @return The timestamp of the new consensus state.
// solhint-disable-next-line code-complexity
function handleSP1UpdateClientAndMembership(
Height calldata proofHeight,
bytes memory proofBytes,
Expand Down Expand Up @@ -270,7 +277,12 @@ contract SP1ICS07Tendermint is

validateUpdateClientPublicValues(output.updateClientOutput);

verifySP1Proof(proof.sp1Proof);
// We avoid the cost of caching for single kv pairs, as reusing the proof is not necessary
if (output.kvPairs.length == 1) {
verifySP1Proof(proof.sp1Proof);
} else {
verifySP1ProofCached(proof.sp1Proof);
}
}

// check update result
Expand Down Expand Up @@ -438,12 +450,25 @@ contract SP1ICS07Tendermint is
}
}

/// @notice Verifies the SP1 proof.
/// @notice Verifies the SP1 proof
/// @param proof The SP1 proof.
function verifySP1Proof(SP1Proof memory proof) private view {
VERIFIER.verifyProof(proof.vKey, proof.publicValues, proof.proof);
}

/// @notice Verifies the SP1 proof and stores the hash of the proof.
/// @dev If the proof is already cached, it does not verify the proof again.
/// @param proof The SP1 proof.
function verifySP1ProofCached(SP1Proof memory proof) private {
bytes32 proofHash = keccak256(abi.encode(proof));
if (verifiedProofs[proofHash]) {
return;
}

VERIFIER.verifyProof(proof.vKey, proof.publicValues, proof.proof);
verifiedProofs[proofHash] = true;
}

/// @notice A dummy function to generate the ABI for the parameters.
/// @param o1 The MembershipOutput.
/// @param o2 The UcAndMembershipOutput.
Expand Down
37 changes: 32 additions & 5 deletions contracts/test/Membership.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,51 @@ contract SP1ICS07MembershipTest is MembershipTest {

ics07Tendermint.membership(membershipMsg);

// to console
console.log("VerifyMultiMembership gas used: ", vm.lastCallGas().gasTotalUsed);
console.log("VerifyMembership gas used: ", vm.lastCallGas().gasTotalUsed);
}

// Modify the proof to make it a non-membership proof.
function test_ValidVerifyNonMembership() public {
MsgMembership memory membershipMsg = MsgMembership({
MsgMembership memory nonMembershipMsg = MsgMembership({
proof: abi.encode(fixture.membershipProof),
proofHeight: fixture.proofHeight,
path: verifyNonMembershipPath,
value: bytes("")
});

ics07Tendermint.membership(membershipMsg);
// to console
ics07Tendermint.membership(nonMembershipMsg);

console.log("VerifyNonMembership gas used: ", vm.lastCallGas().gasTotalUsed);
}

function test_ValidCachedMembership() public {
MsgMembership memory membershipMsg = MsgMembership({
proof: abi.encode(fixture.membershipProof),
proofHeight: fixture.proofHeight,
path: verifyMembershipPath,
value: verifyMembershipValue()
});

ics07Tendermint.membership(membershipMsg);

// resubmit the same proof
ics07Tendermint.membership(membershipMsg);

console.log("Cached VerifyMembership gas used: ", vm.lastCallGas().gasTotalUsed);

// resubmit the same proof as non-membership
MsgMembership memory nonMembershipMsg = MsgMembership({
proof: abi.encode(fixture.membershipProof),
proofHeight: fixture.proofHeight,
path: verifyNonMembershipPath,
value: bytes("")
});

ics07Tendermint.membership(nonMembershipMsg);

console.log("Cached VerifyNonMembership gas used: ", vm.lastCallGas().gasTotalUsed);
}

// Confirm that submitting an invalid proof with the real verifier fails.
function test_Invalid_VerifyMembership() public {
SP1MembershipProof memory proofMsg = proof;
Expand Down
48 changes: 44 additions & 4 deletions contracts/test/UcAndMembership.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ contract SP1ICS07UpdateClientAndMembershipTest is MembershipTest {
// run verify
ics07Tendermint.membership(membershipMsg);

// to console
console.log("UpdateClientAndVerifyMembership gas used: ", vm.lastCallGas().gasTotalUsed);

ClientState memory clientState = ics07Tendermint.getClientState();
Expand All @@ -62,17 +61,16 @@ contract SP1ICS07UpdateClientAndMembershipTest is MembershipTest {
// set a correct timestamp
vm.warp(output.updateClientOutput.env.now + 300);

MsgMembership memory membershipMsg = MsgMembership({
MsgMembership memory nonMembershipMsg = MsgMembership({
proof: abi.encode(fixture.membershipProof),
proofHeight: fixture.proofHeight,
path: verifyNonMembershipPath,
value: bytes("")
});

// run verify
ics07Tendermint.membership(membershipMsg);
ics07Tendermint.membership(nonMembershipMsg);

// to console
console.log("UpdateClientAndVerifyNonMembership gas used: ", vm.lastCallGas().gasTotalUsed);

ClientState memory clientState = ics07Tendermint.getClientState();
Expand All @@ -84,6 +82,48 @@ contract SP1ICS07UpdateClientAndMembershipTest is MembershipTest {
assert(consensusHash == keccak256(abi.encode(output.updateClientOutput.newConsensusState)));
}

// Confirm that submitting a real proof passes the verifier.
function test_Valid_CachedUpdateClientAndMembership() public {
UcAndMembershipOutput memory output = abi.decode(proof.sp1Proof.publicValues, (UcAndMembershipOutput));
// set a correct timestamp
vm.warp(output.updateClientOutput.env.now + 300);

MsgMembership memory membershipMsg = MsgMembership({
proof: abi.encode(fixture.membershipProof),
proofHeight: fixture.proofHeight,
path: verifyMembershipPath,
value: verifyMembershipValue()
});

// run verify
ics07Tendermint.membership(membershipMsg);

ClientState memory clientState = ics07Tendermint.getClientState();
assert(clientState.latestHeight.revisionHeight == output.updateClientOutput.newHeight.revisionHeight);
assert(clientState.isFrozen == false);

bytes32 consensusHash =
ics07Tendermint.getConsensusStateHash(output.updateClientOutput.newHeight.revisionHeight);
assert(consensusHash == keccak256(abi.encode(output.updateClientOutput.newConsensusState)));

// resubmit the same proof
ics07Tendermint.membership(membershipMsg);

console.log("Cached UpdateClientAndVerifyMembership gas used: ", vm.lastCallGas().gasTotalUsed);

MsgMembership memory nonMembershipMsg = MsgMembership({
proof: abi.encode(fixture.membershipProof),
proofHeight: fixture.proofHeight,
path: verifyNonMembershipPath,
value: bytes("")
});

// run verify
ics07Tendermint.membership(nonMembershipMsg);

console.log("Cached UpdateClientAndNonVerifyMembership gas used: ", vm.lastCallGas().gasTotalUsed);
}

// Confirm that submitting a real proof passes the verifier.
function test_Invalid_UpdateClientAndMembership() public {
UcAndMembershipOutput memory output = abi.decode(proof.sp1Proof.publicValues, (UcAndMembershipOutput));
Expand Down

0 comments on commit c5c7aa3

Please sign in to comment.