Skip to content

Commit

Permalink
Add sets for pending and fulfilled request ids (for tracking of misse…
Browse files Browse the repository at this point in the history
…d requests by agent) and upgrade smart contracts (#7)

* update blocklock implementation contract

* retain decryption key in decryption sender

* upgrade decryption sender with removal of decryption key deletion

* isInFlight should not revert, add an enumerable set of fulfilled and unfulfilled request ids in DecryptionSender

* update isInFlight function to return the opposite of bool isFulfilled

* add unit tests for enumerable set logic

* upgrade DecryptionSender and BlocklockSender smart contracts

* update isInFlight

* update decryption sender

* upgrade smart contract

* update smart contract docs
  • Loading branch information
najienka authored Feb 6, 2025
1 parent 6e369a6 commit d7d8547
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 29 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ This library is designed with modularity and simplicity in mind, allowing develo
| Contract | Address | Network |
|-----------------|---------|------------------|
| BlocklockSender Proxy | 0xfF66908E1d7d23ff62791505b2eC120128918F44 | Filecoin Testnet |
| BlocklockSender Implementation | 0x2c42CAe1B8b7F03A503480B6908ff204fE48C8F5 | Filecoin Testnet |
| BlocklockSender Implementation | 0xb8a5e04A88412190eFF892fcA51123028BCdA12F | Filecoin Testnet |
| DecryptionSender Proxy | 0x9297Bb1d423ef7386C8b2e6B7BdE377977FBedd3 | Filecoin Testnet |
| DecryptionSender Implementation | 0x440ccac6429a5bA45bB814A5253e57832499e8a0 | Filecoin Testnet |
| DecryptionSender Implementation | 0xd8f2dDbBc1a0c948A9F2560a7524C945D765DEaF | Filecoin Testnet |
| SignatureSchemeAddressProvider | 0xD2b5084E68230D609AEaAe5E4cF7df9ebDd6375A | Filecoin Testnet |
| BlocklockSignatureScheme | 0x62C9CF8Ff30177d8479eDaB017f38017bEbf10C2 | Filecoin Testnet |
| MockBlocklockReceiver | 0x6f637EcB3Eaf8bEd0fc597Dc54F477a33BBCA72B | Filecoin Testnet |
Expand Down
10 changes: 1 addition & 9 deletions scripts/mocks/create-timelock-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,10 @@ import { keccak_256 } from "@noble/hashes/sha3";

const RPC_URL = process.env.CALIBRATIONNET_RPC_URL;
const walletAddr = "0x5d84b82b750B996BFC1FA7985D90Ae8Fbe773364"
// const walletAddr = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
const blocklockSenderAddr = "0xfF66908E1d7d23ff62791505b2eC120128918F44"
const decryptionSenderAddr = "0x9297Bb1d423ef7386C8b2e6B7BdE377977FBedd3";
const mockBlocklockReceiverAddr = "0x6f637EcB3Eaf8bEd0fc597Dc54F477a33BBCA72B";

// const RPC_URL = "http://127.0.0.1:8545"
// const walletAddr = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";

// const blocklockSenderAddr = "0xC6bA8C3233eCF65B761049ef63466945c362EdD2";
// const decryptionSenderAddr = "0xbCF26943C0197d2eE0E5D05c716Be60cc2761508";
// const mockBlocklockReceiverAddr = "0x0b48aF34f4c854F5ae1A3D587da471FeA45bAD52";

const BLOCKLOCK_IBE_OPTS: IbeOpts = {
hash: keccak_256,
k: 128,
Expand Down Expand Up @@ -122,7 +114,7 @@ async function main() {
const mockBlocklockReceiver = MockBlocklockReceiver__factory.connect(mockBlocklockReceiverAddr, signer);

// create a timelock request from mockBlocklockReceiver contract and check it is fulfilled by blocklock agent
const blockHeight = BigInt(await provider.getBlockNumber() + 5);
const blockHeight = BigInt(await provider.getBlockNumber() + 6);
const msg = ethers.parseEther("4");
const abiCoder = AbiCoder.defaultAbiCoder();
const msgBytes = abiCoder.encode(["uint256"], [msg]);
Expand Down
19 changes: 11 additions & 8 deletions src/blocklock/BlocklockSender.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ contract BlocklockSender is
bytes public constant DST_H3 = "BLOCKLOCK_BN254_XMD:KECCAK-256_H3_";
bytes public constant DST_H4 = "BLOCKLOCK_BN254_XMD:KECCAK-256_H4_";

// Mapping from decryption requestID to blocklock status
mapping(uint256 => TypesLib.BlocklockRequest) public blocklockRequests;
// Mapping from decryption requestID to conditional decryption request
mapping(uint256 => TypesLib.BlocklockRequest) public blocklockRequestsWithDecryptionKey;

event BlocklockRequested(
uint256 indexed requestID,
Expand Down Expand Up @@ -98,7 +98,7 @@ contract BlocklockSender is
r.decryptionRequestID = decryptionRequestID;

// Store the signature requestID for this blockHeight
blocklockRequests[decryptionRequestID] = r;
blocklockRequestsWithDecryptionKey[decryptionRequestID] = r;

emit BlocklockRequested(decryptionRequestID, blockHeight, ciphertext, msg.sender, block.timestamp);
return decryptionRequestID;
Expand All @@ -111,7 +111,7 @@ contract BlocklockSender is
internal
override
{
TypesLib.BlocklockRequest memory r = blocklockRequests[decryptionRequestID];
TypesLib.BlocklockRequest memory r = blocklockRequestsWithDecryptionKey[decryptionRequestID];
require(r.decryptionRequestID > 0, "no matching blocklock request for that id");

r.signature = signature;
Expand All @@ -124,7 +124,7 @@ contract BlocklockSender is
revert BlocklockCallbackFailed(decryptionRequestID);
} else {
emit BlocklockCallbackSuccess(decryptionRequestID, r.blockHeight, r.ciphertext, decryptionKey);
blocklockRequests[decryptionRequestID].decryptionKey = decryptionKey;
blocklockRequestsWithDecryptionKey[decryptionRequestID].decryptionKey = decryptionKey;
}
}

Expand Down Expand Up @@ -181,8 +181,11 @@ contract BlocklockSender is
* @dev See {ISignatureSender-isInFlight}.
*/
function isInFlight(uint256 requestID) external view returns (bool) {
uint256 signatureRequestID = blocklockRequests[requestID].decryptionRequestID;
require(signatureRequestID > 0, "blocklock request not found");
uint256 signatureRequestID = blocklockRequestsWithDecryptionKey[requestID].decryptionRequestID;

if (signatureRequestID == 0) {
return false;
}

return decryptionSender.isInFlight(signatureRequestID);
}
Expand All @@ -191,7 +194,7 @@ contract BlocklockSender is
* @dev See {IBlocklockSender-getRequest}.
*/
function getRequest(uint256 requestID) external view returns (TypesLib.BlocklockRequest memory) {
TypesLib.BlocklockRequest memory r = blocklockRequests[requestID];
TypesLib.BlocklockRequest memory r = blocklockRequestsWithDecryptionKey[requestID];
require(r.decryptionRequestID > 0, "invalid requestID");

return r;
Expand Down
43 changes: 34 additions & 9 deletions src/decryption-requests/DecryptionSender.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {AccessControlEnumerableUpgradeable} from
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {ContextUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import {BLS} from "../libraries/BLS.sol";
import {TypesLib} from "../libraries/TypesLib.sol";
Expand Down Expand Up @@ -35,15 +36,21 @@ contract DecryptionSender is
AccessControlEnumerableUpgradeable
{
using BytesLib for bytes;
using EnumerableSet for EnumerableSet.UintSet;

bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");

uint256 public lastRequestID = 0;
BLS.PointG2 private publicKey = BLS.PointG2({x: [uint256(0), uint256(0)], y: [uint256(0), uint256(0)]});
mapping(uint256 => TypesLib.DecryptionRequest) public requestsInFlight;

// Mapping from decryption requestID to conditional decryption request
mapping(uint256 => TypesLib.DecryptionRequest) public requests;

ISignatureSchemeAddressProvider public signatureSchemeAddressProvider;

EnumerableSet.UintSet private fulfilledRequestIds;
EnumerableSet.UintSet private unfulfilledRequestIds;

event SignatureSchemeAddressProviderUpdated(address indexed newSignatureSchemeAddressProvider);
event DecryptionRequested(
uint256 indexed requestID,
Expand Down Expand Up @@ -118,14 +125,16 @@ contract DecryptionSender is
address schemeContractAddress = signatureSchemeAddressProvider.getSignatureSchemeAddress(schemeID);
require(schemeContractAddress > address(0), "invalid signature scheme");

requestsInFlight[lastRequestID] = TypesLib.DecryptionRequest({
requests[lastRequestID] = TypesLib.DecryptionRequest({
schemeID: schemeID,
ciphertext: ciphertext,
condition: condition,
decryptionKey: hex"",
signature: hex"",
callback: msg.sender
callback: msg.sender,
isFulfilled: false
});
unfulfilledRequestIds.add(lastRequestID);

emit DecryptionRequested(lastRequestID, msg.sender, schemeID, condition, ciphertext, block.timestamp);

Expand All @@ -140,7 +149,7 @@ contract DecryptionSender is
onlyOwner
{
require(isInFlight(requestID), "No request with specified requestID");
TypesLib.DecryptionRequest memory request = requestsInFlight[requestID];
TypesLib.DecryptionRequest memory request = requests[requestID];

string memory schemeID = request.schemeID;
address schemeContractAddress = signatureSchemeAddressProvider.getSignatureSchemeAddress(schemeID);
Expand All @@ -161,7 +170,11 @@ contract DecryptionSender is
revert DecryptionReceiverCallbackFailed(requestID);
} else {
emit DecryptionReceiverCallbackSuccess(requestID, decryptionKey, signature);
delete requestsInFlight[requestID];
requests[requestID].decryptionKey = decryptionKey;
requests[requestID].isFulfilled = true;

unfulfilledRequestIds.remove(requestID);
fulfilledRequestIds.add(requestID);
}
}

Expand Down Expand Up @@ -191,14 +204,26 @@ contract DecryptionSender is
* @dev See {IDecryptionSender-isInFlight}.
*/
function isInFlight(uint256 requestID) public view returns (bool) {
return requestsInFlight[requestID].callback != address(0);
return unfulfilledRequestIds.contains(requestID);
}

/**
* @dev See {IDecryptionSender-getRequestInFlight}.
* @dev See {IDecryptionSender-getRequest}.
*/
function getRequestInFlight(uint256 requestID) external view returns (TypesLib.DecryptionRequest memory) {
return requestsInFlight[requestID];
function getRequest(uint256 requestID) external view returns (TypesLib.DecryptionRequest memory) {
return requests[requestID];
}

function getAllFulfilledRequestIds() external view returns (uint256[] memory) {
return fulfilledRequestIds.values();
}

function getAllUnfulfilledRequestIds() external view returns (uint256[] memory) {
return unfulfilledRequestIds.values();
}

function getCountOfUnfulfilledRequestIds() external view returns (uint256) {
return unfulfilledRequestIds.length();
}

/**
Expand Down
20 changes: 19 additions & 1 deletion src/interfaces/IDecryptionSender.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ interface IDecryptionSender {
* @param requestId The ID of the request to retrieve.
* @return The Request struct corresponding to the given requestId.
*/
function getRequestInFlight(uint256 requestId) external view returns (TypesLib.DecryptionRequest memory);
function getRequest(uint256 requestId) external view returns (TypesLib.DecryptionRequest memory);

/**
* @notice Verifies whether a specific request is in flight or not.
Expand All @@ -67,6 +67,24 @@ interface IDecryptionSender {
*/
function getPublicKeyBytes() external view returns (bytes memory);

/**
* @notice Returns all the fulfilled request ids.
* @return A uint array representing a set containing all fulfilled request ids.
*/
function getAllFulfilledRequestIds() external view returns (uint256[] memory);

/**
* @notice Returns all the request ids that are yet to be fulfilled.
* @return A uint array representing a set containing all request ids that are yet to be fulfilled.
*/
function getAllUnfulfilledRequestIds() external view returns (uint256[] memory);

/**
* @notice Returns count of all the request ids that are yet to be fulfilled.
* @return A uint representing a count of all request ids that are yet to be fulfilled.
*/
function getCountOfUnfulfilledRequestIds() external view returns (uint256);

/**
* @dev Returns the version number of the upgradeable contract.
*/
Expand Down
1 change: 1 addition & 0 deletions src/libraries/TypesLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@ library TypesLib {
bytes decryptionKey;
bytes signature;
address callback;
bool isFulfilled;
}
}
75 changes: 75 additions & 0 deletions test/hardhat/Blocklock.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,61 @@ describe("BlocklockSender", function () {
).to.be.reverted;
});

it("enumerable set can track multiple requests", async function () {
let numberOfPendingRequests = await decryptionSender.getCountOfUnfulfilledRequestIds();
let pendingRequestIds = await decryptionSender.getAllUnfulfilledRequestIds();
let nonPendingRequestIds = await decryptionSender.getAllFulfilledRequestIds();

expect(numberOfPendingRequests).to.be.equal(0);
expect(pendingRequestIds.length).to.be.equal(0);
expect(nonPendingRequestIds.length).to.be.equal(0);

let blockHeight = await ethers.provider.getBlockNumber();

const msg = "mainnet launch soon";
const msgBytes = AbiCoder.defaultAbiCoder().encode(["string"], [msg]);
const encodedMessage = getBytes(msgBytes);

const ct = encrypt(encodedMessage, BigInt(blockHeight + 2), BLOCKLOCK_DEFAULT_PUBLIC_KEY);
const ct2 = encrypt(encodedMessage, BigInt(blockHeight + 3), BLOCKLOCK_DEFAULT_PUBLIC_KEY);

let tx = await blocklockStringReceiver
.connect(owner)
.createTimelockRequest(BigInt(blockHeight + 2), encodeCiphertextToSolidity(ct));
let receipt = await tx.wait(1);
if (!receipt) {
throw new Error("transaction has not been mined");
}

tx = await blocklockStringReceiver
.connect(owner)
.createTimelockRequest(BigInt(blockHeight + 3), encodeCiphertextToSolidity(ct2));
receipt = await tx.wait(1);
if (!receipt) {
throw new Error("transaction has not been mined");
}

numberOfPendingRequests = await decryptionSender.getCountOfUnfulfilledRequestIds();
pendingRequestIds = await decryptionSender.getAllUnfulfilledRequestIds();
nonPendingRequestIds = await decryptionSender.getAllFulfilledRequestIds();

expect(numberOfPendingRequests).to.be.equal(2);
expect(pendingRequestIds.length).to.be.equal(2);
expect(nonPendingRequestIds.length).to.be.equal(0);
expect(pendingRequestIds[0]).to.be.equal(1);
expect(pendingRequestIds[1]).to.be.equal(2);
expect(await decryptionSender.isInFlight(1)).to.be.equal(true);
});

it("can request blocklock decryption from user contract for string and receive decryption key callback", async function () {
let numberOfPendingRequests = await decryptionSender.getCountOfUnfulfilledRequestIds();
let pendingRequestIds = await decryptionSender.getAllUnfulfilledRequestIds();
let nonPendingRequestIds = await decryptionSender.getAllFulfilledRequestIds();

expect(numberOfPendingRequests).to.be.equal(0);
expect(pendingRequestIds.length).to.be.equal(0);
expect(nonPendingRequestIds.length).to.be.equal(0);

let blockHeight = await ethers.provider.getBlockNumber();

const msg = "mainnet launch soon";
Expand All @@ -441,6 +495,16 @@ describe("BlocklockSender", function () {
decryptionSenderIface.getEvent("DecryptionRequested"),
);

numberOfPendingRequests = await decryptionSender.getCountOfUnfulfilledRequestIds();
pendingRequestIds = await decryptionSender.getAllUnfulfilledRequestIds();
nonPendingRequestIds = await decryptionSender.getAllFulfilledRequestIds();

expect(numberOfPendingRequests).to.be.equal(1);
expect(pendingRequestIds.length).to.be.equal(1);
expect(nonPendingRequestIds.length).to.be.equal(0);

expect(pendingRequestIds[0]).to.be.equal(1);

console.log("callback and blocklock address", callback, await blocklock.getAddress());

let req = await blocklock.getRequest(BigInt(requestID));
Expand Down Expand Up @@ -480,6 +544,17 @@ describe("BlocklockSender", function () {
iface.getEvent("BlocklockCallbackSuccess"),
);

numberOfPendingRequests = await decryptionSender.getCountOfUnfulfilledRequestIds();
pendingRequestIds = await decryptionSender.getAllUnfulfilledRequestIds();
nonPendingRequestIds = await decryptionSender.getAllFulfilledRequestIds();

expect(numberOfPendingRequests).to.be.equal(0);
expect(pendingRequestIds.length).to.be.equal(0);
expect(nonPendingRequestIds.length).to.be.equal(1);

expect(nonPendingRequestIds[0]).to.be.equal(1);
expect(await decryptionSender.isInFlight(1)).to.be.equal(false);

// ciphertext should not be deleted after successful callback
req = await blocklock.getRequest(BigInt(requestID));
expect(req.blockHeight).to.be.equal(Number(decryptionBlockHeight));
Expand Down

0 comments on commit d7d8547

Please sign in to comment.