diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index e033fc779a52..d11aecbc1ab3 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -47,7 +47,7 @@ library Constants { uint256 internal constant FUNCTION_TREE_HEIGHT = 5; uint256 internal constant CONTRACT_TREE_HEIGHT = 16; uint256 internal constant NOTE_HASH_TREE_HEIGHT = 32; - uint256 internal constant PUBLIC_DATA_TREE_HEIGHT = 254; + uint256 internal constant PUBLIC_DATA_TREE_HEIGHT = 40; uint256 internal constant NULLIFIER_TREE_HEIGHT = 20; uint256 internal constant L1_TO_L2_MSG_TREE_HEIGHT = 16; uint256 internal constant ROLLUP_VK_TREE_HEIGHT = 8; @@ -56,8 +56,10 @@ library Constants { uint256 internal constant NOTE_HASH_SUBTREE_HEIGHT = 7; uint256 internal constant NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH = 25; uint256 internal constant NULLIFIER_SUBTREE_HEIGHT = 7; + uint256 internal constant PUBLIC_DATA_SUBTREE_HEIGHT = 4; uint256 internal constant ARCHIVE_HEIGHT = 16; uint256 internal constant NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH = 13; + uint256 internal constant PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH = 36; uint256 internal constant L1_TO_L2_MSG_SUBTREE_HEIGHT = 4; uint256 internal constant L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH = 12; uint256 internal constant FUNCTION_SELECTOR_NUM_BYTES = 4; diff --git a/l1-contracts/src/core/libraries/Decoder.sol b/l1-contracts/src/core/libraries/Decoder.sol index c1c0e4a828e1..c7b51b30b0ee 100644 --- a/l1-contracts/src/core/libraries/Decoder.sol +++ b/l1-contracts/src/core/libraries/Decoder.sol @@ -32,39 +32,41 @@ import {Hash} from "./Hash.sol"; * | 0x00c4 | 0x04 | startNullifierTreeSnapshot.nextAvailableLeafIndex * | 0x00c8 | 0x20 | startContractTreeSnapshot.root * | 0x00e8 | 0x04 | startContractTreeSnapshot.nextAvailableLeafIndex - * | 0x00ec | 0x20 | startPublicDataTreeRoot - * | 0x010c | 0x20 | startL1ToL2MessageTreeSnapshot.root - * | 0x012c | 0x04 | startL1ToL2MessageTreeSnapshot.nextAvailableLeafIndex - * | 0x0130 | 0x20 | startArchiveSnapshot.root - * | 0x0150 | 0x04 | startArchiveSnapshot.nextAvailableLeafIndex - * | 0x0154 | 0x20 | endNoteHashTreeSnapshot.root - * | 0x0174 | 0x04 | endNoteHashTreeSnapshot.nextAvailableLeafIndex - * | 0x0178 | 0x20 | endNullifierTreeSnapshot.root - * | 0x0198 | 0x04 | endNullifierTreeSnapshot.nextAvailableLeafIndex - * | 0x019c | 0x20 | endContractTreeSnapshot.root - * | 0x01bc | 0x04 | endContractTreeSnapshot.nextAvailableLeafIndex - * | 0x01c0 | 0x20 | endPublicDataTreeRoot - * | 0x01e0 | 0x20 | endL1ToL2MessageTreeSnapshot.root - * | 0x0200 | 0x04 | endL1ToL2MessageTreeSnapshot.nextAvailableLeafIndex - * | 0x0204 | 0x20 | endArchiveSnapshot.root - * | 0x0224 | 0x04 | endArchiveSnapshot.nextAvailableLeafIndex - * | 0x0228 | 0x04 | len(newCommitments) (denoted a) - * | 0x022c | a * 0x20 | newCommitments - * | 0x022c + a * 0x20 | 0x04 | len(newNullifiers) (denoted b) - * | 0x0230 + a * 0x20 | b * 0x20 | newNullifiers - * | 0x0230 + a * 0x20 + b * 0x20 | 0x04 | len(newPublicDataWrites) (denoted c) - * | 0x0234 + a * 0x20 + b * 0x20 | c * 0x40 | newPublicDataWrites - * | 0x0234 + a * 0x20 + b * 0x20 + c * 0x40 | 0x04 | len(newL2ToL1Msgs) (denoted d) - * | 0x0238 + a * 0x20 + b * 0x20 + c * 0x40 | d * 0x20 | newL2ToL1Msgs - * | 0x0238 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 | 0x04 | len(contracts) (denoted e) - * | 0x023c + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 | e * 0x20 | newContracts - * | 0x023c + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x20 | e * 0x34 | newContractsData - * | 0x023c + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 | 0x04 | len(newL1ToL2Msgs) (denoted f) - * | 0x0240 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 | f * 0x20 | newL1ToL2Msgs - * | 0x0240 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 + f * 0x20 | 0x04 | byteLen(newEncryptedLogs) (denoted g) - * | 0x0244 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 + f * 0x20 | g | newEncryptedLogs - * | 0x0244 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 + f * 0x20 + g | 0x04 | byteLen(newUnencryptedLogs) (denoted h) - * | 0x0248 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 + f * 0x20 + g | h | newUnencryptedLogs + * | 0x00ec | 0x20 | startPublicDataTreeSnapshot.root + * | 0x010c | 0x04 | startPublicDataTreeSnapshot.nextAvailableLeafIndex + * | 0x0110 | 0x20 | startL1ToL2MessageTreeSnapshot.root + * | 0x0130 | 0x04 | startL1ToL2MessageTreeSnapshot.nextAvailableLeafIndex + * | 0x0134 | 0x20 | startArchiveSnapshot.root + * | 0x0154 | 0x04 | startArchiveSnapshot.nextAvailableLeafIndex + * | 0x0158 | 0x20 | endNoteHashTreeSnapshot.root + * | 0x0178 | 0x04 | endNoteHashTreeSnapshot.nextAvailableLeafIndex + * | 0x017c | 0x20 | endNullifierTreeSnapshot.root + * | 0x019c | 0x04 | endNullifierTreeSnapshot.nextAvailableLeafIndex + * | 0x01a0 | 0x20 | endContractTreeSnapshot.root + * | 0x01c0 | 0x04 | endContractTreeSnapshot.nextAvailableLeafIndex + * | 0x01c4 | 0x20 | endPublicDataTreeSnapshot.root + * | 0x01e4 | 0x04 | endPublicDataTreeSnapshot.nextAvailableLeafIndex + * | 0x01e8 | 0x20 | endL1ToL2MessageTreeSnapshot.root + * | 0x0208 | 0x04 | endL1ToL2MessageTreeSnapshot.nextAvailableLeafIndex + * | 0x020c | 0x20 | endArchiveSnapshot.root + * | 0x022c | 0x04 | endArchiveSnapshot.nextAvailableLeafIndex + * | 0x0230 | 0x04 | len(newCommitments) (denoted a) + * | 0x0234 | a * 0x20 | newCommitments + * | 0x0234 + a * 0x20 | 0x04 | len(newNullifiers) (denoted b) + * | 0x0238 + a * 0x20 | b * 0x20 | newNullifiers + * | 0x0238 + a * 0x20 + b * 0x20 | 0x04 | len(newPublicDataWrites) (denoted c) + * | 0x023c + a * 0x20 + b * 0x20 | c * 0x40 | newPublicDataWrites + * | 0x023c + a * 0x20 + b * 0x20 + c * 0x40 | 0x04 | len(newL2ToL1Msgs) (denoted d) + * | 0x0240 + a * 0x20 + b * 0x20 + c * 0x40 | d * 0x20 | newL2ToL1Msgs + * | 0x0240 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 | 0x04 | len(contracts) (denoted e) + * | 0x0244 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 | e * 0x20 | newContracts + * | 0x0244 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x20 | e * 0x34 | newContractsData + * | 0x0244 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 | 0x04 | len(newL1ToL2Msgs) (denoted f) + * | 0x0248 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 | f * 0x20 | newL1ToL2Msgs + * | 0x0248 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 + f * 0x20 | 0x04 | byteLen(newEncryptedLogs) (denoted g) + * | 0x024c + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 + f * 0x20 | g | newEncryptedLogs + * | 0x024c + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 + f * 0x20 + g | 0x04 | byteLen(newUnencryptedLogs) (denoted h) + * | 0x0250 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 + f * 0x20 + g | h | newUnencryptedLogs * | --- | --- | --- */ library Decoder { @@ -97,7 +99,7 @@ library Decoder { uint256 private constant START_TREES_BLOCK_HEADER_OFFSET = 0x80; // The size of the block header elements - uint256 private constant TREES_BLOCK_HEADER_SIZE = 0xd4; + uint256 private constant TREES_BLOCK_HEADER_SIZE = 0xd8; // Where the end of trees metadata begins in the block uint256 private constant END_TREES_BLOCK_HEADER_OFFSET = diff --git a/l1-contracts/test/Decoder.t.sol b/l1-contracts/test/Decoder.t.sol index 0a01e0431c8d..c353bd517563 100644 --- a/l1-contracts/test/Decoder.t.sol +++ b/l1-contracts/test/Decoder.t.sol @@ -29,7 +29,7 @@ contract DecoderTest is Test { hex"0000000000000000000000000000000000000000000000000000000000007a6900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000020efbe2c7b675f26ab71689279908bbab33a6963e7e0dcb80e4c46583d094113000000002adc67712c2f7afc4e827551236adb46a693d572fbb29f3993dbedbef8d2d87d0000002027378c30a97c642a3b78bd34c54beac15a4dadcd7a3378e66d2384c485fda541000000002b72136df9bc7dc9cbfe6b84ec743e8e1d73dd93aecfa79f18afb86be977d3eb27378c30a97c642a3b78bd34c54beac15a4dadcd7a3378e66d2384c485fda541000000000103babeb39cf7cb3fd320ec225367ac5e834aaec3e7b48bb3239d0da39fa4a20000000120efbe2c7b675f26ab71689279908bbab33a6963e7e0dcb80e4c46583d094113000000402adc67712c2f7afc4e827551236adb46a693d572fbb29f3993dbedbef8d2d87d0000006027378c30a97c642a3b78bd34c54beac15a4dadcd7a3378e66d2384c485fda541000000042b72136df9bc7dc9cbfe6b84ec743e8e1d73dd93aecfa79f18afb86be977d3eb27378c30a97c642a3b78bd34c54beac15a4dadcd7a3378e66d2384c485fda5410000001019133ced5bd7348dae0f82a7195910ca141c7b5c1894a7c5f9fa53290c79f3ed0000000200000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000001000000000000000000000000000000000"; bytes internal block_mixed_1 = - hex"0000000000000000000000000000000000000000000000000000000000007a6900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000020efbe2c7b675f26ab71689279908bbab33a6963e7e0dcb80e4c46583d094113000000002adc67712c2f7afc4e827551236adb46a693d572fbb29f3993dbedbef8d2d87d0000002027378c30a97c642a3b78bd34c54beac15a4dadcd7a3378e66d2384c485fda541000000002b72136df9bc7dc9cbfe6b84ec743e8e1d73dd93aecfa79f18afb86be977d3eb27378c30a97c642a3b78bd34c54beac15a4dadcd7a3378e66d2384c485fda541000000000103babeb39cf7cb3fd320ec225367ac5e834aaec3e7b48bb3239d0da39fa4a200000001235f4e41a2440aa28f9ac14e7eed447ddd2e539179cee4c0941e6e408d2443e50000004026f32989eb2870f2ed00774f54a82e8266fc2a2b3392b64e8199aacac71aabea000000600b6abbab461cfb072b267bfc1ecf8dc3c943736341baf11c5c829e345c49b5090000000429c0d0effa4242b8d2e372cfcdfa8bb57160715fa3640a3404a9bba93725a1072fbbd267a1c9b23b3ac1609b2c2a7961a0338c56d62607d5f8d6b6a29e7fe4cb000000102d81b3e2140328e322ad47f083bfb89d61aa26a26795d34e7442006f939663b300000002000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000012100000000000000000000000000000000000000000000000000000000000001220000000000000000000000000000000000000000000000000000000000000123000000000000000000000000000000000000000000000000000000000000012400000000000000000000000000000000000000000000000000000000000001250000000000000000000000000000000000000000000000000000000000000126000000000000000000000000000000000000000000000000000000000000012700000000000000000000000000000000000000000000000000000000000001280000000000000000000000000000000000000000000000000000000000000129000000000000000000000000000000000000000000000000000000000000012a000000000000000000000000000000000000000000000000000000000000012b000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000000000000000000000000000000000000000012d000000000000000000000000000000000000000000000000000000000000012e000000000000000000000000000000000000000000000000000000000000012f0000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000014100000000000000000000000000000000000000000000000000000000000001420000000000000000000000000000000000000000000000000000000000000143000000000000000000000000000000000000000000000000000000000000014400000000000000000000000000000000000000000000000000000000000001450000000000000000000000000000000000000000000000000000000000000146000000000000000000000000000000000000000000000000000000000000014700000000000000000000000000000000000000000000000000000000000001480000000000000000000000000000000000000000000000000000000000000149000000000000000000000000000000000000000000000000000000000000014a000000000000000000000000000000000000000000000000000000000000014b000000000000000000000000000000000000000000000000000000000000014c000000000000000000000000000000000000000000000000000000000000014d000000000000000000000000000000000000000000000000000000000000014e000000000000000000000000000000000000000000000000000000000000014f0000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000016100000000000000000000000000000000000000000000000000000000000001620000000000000000000000000000000000000000000000000000000000000163000000000000000000000000000000000000000000000000000000000000016400000000000000000000000000000000000000000000000000000000000001650000000000000000000000000000000000000000000000000000000000000166000000000000000000000000000000000000000000000000000000000000016700000000000000000000000000000000000000000000000000000000000001680000000000000000000000000000000000000000000000000000000000000169000000000000000000000000000000000000000000000000000000000000016a000000000000000000000000000000000000000000000000000000000000016b000000000000000000000000000000000000000000000000000000000000016c000000000000000000000000000000000000000000000000000000000000016d000000000000000000000000000000000000000000000000000000000000016e000000000000000000000000000000000000000000000000000000000000016f0000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000018100000000000000000000000000000000000000000000000000000000000001820000000000000000000000000000000000000000000000000000000000000183000000000000000000000000000000000000000000000000000000000000018400000000000000000000000000000000000000000000000000000000000001850000000000000000000000000000000000000000000000000000000000000186000000000000000000000000000000000000000000000000000000000000018700000000000000000000000000000000000000000000000000000000000001880000000000000000000000000000000000000000000000000000000000000189000000000000000000000000000000000000000000000000000000000000018a000000000000000000000000000000000000000000000000000000000000018b000000000000000000000000000000000000000000000000000000000000018c000000000000000000000000000000000000000000000000000000000000018d000000000000000000000000000000000000000000000000000000000000018e000000000000000000000000000000000000000000000000000000000000018f000000400000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000022100000000000000000000000000000000000000000000000000000000000002220000000000000000000000000000000000000000000000000000000000000223000000000000000000000000000000000000000000000000000000000000022400000000000000000000000000000000000000000000000000000000000002250000000000000000000000000000000000000000000000000000000000000226000000000000000000000000000000000000000000000000000000000000022700000000000000000000000000000000000000000000000000000000000002280000000000000000000000000000000000000000000000000000000000000229000000000000000000000000000000000000000000000000000000000000022a000000000000000000000000000000000000000000000000000000000000022b000000000000000000000000000000000000000000000000000000000000022c000000000000000000000000000000000000000000000000000000000000022d000000000000000000000000000000000000000000000000000000000000022e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000024100000000000000000000000000000000000000000000000000000000000002420000000000000000000000000000000000000000000000000000000000000243000000000000000000000000000000000000000000000000000000000000024400000000000000000000000000000000000000000000000000000000000002450000000000000000000000000000000000000000000000000000000000000246000000000000000000000000000000000000000000000000000000000000024700000000000000000000000000000000000000000000000000000000000002480000000000000000000000000000000000000000000000000000000000000249000000000000000000000000000000000000000000000000000000000000024a000000000000000000000000000000000000000000000000000000000000024b000000000000000000000000000000000000000000000000000000000000024c000000000000000000000000000000000000000000000000000000000000024d000000000000000000000000000000000000000000000000000000000000024e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000026100000000000000000000000000000000000000000000000000000000000002620000000000000000000000000000000000000000000000000000000000000263000000000000000000000000000000000000000000000000000000000000026400000000000000000000000000000000000000000000000000000000000002650000000000000000000000000000000000000000000000000000000000000266000000000000000000000000000000000000000000000000000000000000026700000000000000000000000000000000000000000000000000000000000002680000000000000000000000000000000000000000000000000000000000000269000000000000000000000000000000000000000000000000000000000000026a000000000000000000000000000000000000000000000000000000000000026b000000000000000000000000000000000000000000000000000000000000026c000000000000000000000000000000000000000000000000000000000000026d000000000000000000000000000000000000000000000000000000000000026e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000028100000000000000000000000000000000000000000000000000000000000002820000000000000000000000000000000000000000000000000000000000000283000000000000000000000000000000000000000000000000000000000000028400000000000000000000000000000000000000000000000000000000000002850000000000000000000000000000000000000000000000000000000000000286000000000000000000000000000000000000000000000000000000000000028700000000000000000000000000000000000000000000000000000000000002880000000000000000000000000000000000000000000000000000000000000289000000000000000000000000000000000000000000000000000000000000028a000000000000000000000000000000000000000000000000000000000000028b000000000000000000000000000000000000000000000000000000000000028c000000000000000000000000000000000000000000000000000000000000028d000000000000000000000000000000000000000000000000000000000000028e0000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000520000000000000000000000000000000000000000000000000000000000000052a0000000000000000000000000000000000000000000000000000000000000521000000000000000000000000000000000000000000000000000000000000052b0000000000000000000000000000000000000000000000000000000000000522000000000000000000000000000000000000000000000000000000000000052c0000000000000000000000000000000000000000000000000000000000000523000000000000000000000000000000000000000000000000000000000000052d0000000000000000000000000000000000000000000000000000000000000524000000000000000000000000000000000000000000000000000000000000052e0000000000000000000000000000000000000000000000000000000000000525000000000000000000000000000000000000000000000000000000000000052f00000000000000000000000000000000000000000000000000000000000005260000000000000000000000000000000000000000000000000000000000000530000000000000000000000000000000000000000000000000000000000000052700000000000000000000000000000000000000000000000000000000000005310000000000000000000000000000000000000000000000000000000000000540000000000000000000000000000000000000000000000000000000000000054a0000000000000000000000000000000000000000000000000000000000000541000000000000000000000000000000000000000000000000000000000000054b0000000000000000000000000000000000000000000000000000000000000542000000000000000000000000000000000000000000000000000000000000054c0000000000000000000000000000000000000000000000000000000000000543000000000000000000000000000000000000000000000000000000000000054d0000000000000000000000000000000000000000000000000000000000000544000000000000000000000000000000000000000000000000000000000000054e0000000000000000000000000000000000000000000000000000000000000545000000000000000000000000000000000000000000000000000000000000054f00000000000000000000000000000000000000000000000000000000000005460000000000000000000000000000000000000000000000000000000000000550000000000000000000000000000000000000000000000000000000000000054700000000000000000000000000000000000000000000000000000000000005510000000000000000000000000000000000000000000000000000000000000560000000000000000000000000000000000000000000000000000000000000056a0000000000000000000000000000000000000000000000000000000000000561000000000000000000000000000000000000000000000000000000000000056b0000000000000000000000000000000000000000000000000000000000000562000000000000000000000000000000000000000000000000000000000000056c0000000000000000000000000000000000000000000000000000000000000563000000000000000000000000000000000000000000000000000000000000056d0000000000000000000000000000000000000000000000000000000000000564000000000000000000000000000000000000000000000000000000000000056e0000000000000000000000000000000000000000000000000000000000000565000000000000000000000000000000000000000000000000000000000000056f00000000000000000000000000000000000000000000000000000000000005660000000000000000000000000000000000000000000000000000000000000570000000000000000000000000000000000000000000000000000000000000056700000000000000000000000000000000000000000000000000000000000005710000000000000000000000000000000000000000000000000000000000000580000000000000000000000000000000000000000000000000000000000000058a0000000000000000000000000000000000000000000000000000000000000581000000000000000000000000000000000000000000000000000000000000058b0000000000000000000000000000000000000000000000000000000000000582000000000000000000000000000000000000000000000000000000000000058c0000000000000000000000000000000000000000000000000000000000000583000000000000000000000000000000000000000000000000000000000000058d0000000000000000000000000000000000000000000000000000000000000584000000000000000000000000000000000000000000000000000000000000058e0000000000000000000000000000000000000000000000000000000000000585000000000000000000000000000000000000000000000000000000000000058f000000000000000000000000000000000000000000000000000000000000058600000000000000000000000000000000000000000000000000000000000005900000000000000000000000000000000000000000000000000000000000000587000000000000000000000000000000000000000000000000000000000000059100000008000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003210000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000361000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000003810000000426fcb9639d15aabe6d792e23ab12fb9633046d4be6911a60d64471d7560d3f6809143b7d4943a3485115d37e7596938a16c91b6055f3837640d8c36b8303bb3c06fb5fb553496e5e0b48834087e036acf99d6d935dc2ebf43c82788cb5ed1c6a2f4bd77ac2bb5474d48c2856135d18168cd6f69f77143c60b3cc370319419dac0000000000000000000000000000000000000000000000000000000000001020212121212121212121212121212121212121212100000000000000000000000000000000000000000000000000000000000010404141414141414141414141414141414141414141000000000000000000000000000000000000000000000000000000000000106061616161616161616161616161616161616161610000000000000000000000000000000000000000000000000000000000001080818181818181818181818181818181818181818100000010151de48ca3efbae39f180fe00b8f472ec9f25be10b4f283a87c6d7839353703914c2ea9dedf77698d4afe23bc663263eed0bf9aa3a8b17d9b74812f185610f9e1570cc6641699e3ae87fa258d80a6d853f7b8ccb211dc244d017e2ca6530f8a12806c860af67e9cd50000378411b8c4c4db172ceb2daa862b259b689ccbdc1e005f140c7c95624c8006774279a01ec1ea88617999e4fe6997b6576c4e1c7395a22048b96b586596bd740d0402e15f5577f7ceb5496b65aafc6d89d7c3b34924b0c3f2d50d16279970d682cada30bfa6b29bc0bac0ee2389f6a0444853eccaa932b2a60561da46a58569d71044a84c639e7f88429826e5622581536eb906d9cdd25a2c0a76f7da6924e10751c755227d2535f4ad258b984e78f9f452a853c52300e212d8e2069e4254d81af07744bcbb81121a38f0e2dbed69a523d3fbf85b75c287ca6f33aadbac2e4f058e05924c140d7895a6ed167caf804b710d2ae3ba62b1b51297b3ea37637af6bd56cf33425d95cc5c96e9c2ee3077322fbec86a0c7f32c15d2a888c6cc122e99478c92470a1311635142d82ad7ae67410beeef4ae31f0902ba2fb964922a4610bb18901f7b923885c1d034da5769a48203ae6f0206a92855e2c01ddb3d6553386b5580d681b8230fa4062948668f834f23e0636eaff70aaa64519aafdf4b040bd2f9836e76b9dc13cfec8065dcdf2834d786e06260d10000381000000e00000001bc00000090b1c43c1dd10fa62d46ac721c5529068d286a18a86e22787da7a222925e32a3148915e9b0ed2f8793823e9b4fc52cddfda38739e4a23cbae77fd06d498b9b8e6ea0911e8e23b97a17da9fa3777775672dd21198963bc0bbf6667d10365dd77172004b20053ff5fa88f214c7cfbb1501c5aa7c9bd36a08bcd318a70f71ef4a5837863bcc1e9a539584952bc17cb270e2d9000000907686f00e315ea807d51ecea805379caef8781be53347315fc9510f3e9db44f3e65862c9d964b0007dab956272d11e50d1338bcbaac47886108b376f34d207aa5e94ae1f2adbd06d15590d0cb7061bb79b839a791f90fdfef054e878748fad2123fbb481f7e1d9cfdab406f5e8093badb645fa2cb49203e1bf68f945ca21f5a03027dfe828e99ea4b6cf8ae2daae54bcf000000905510d0f861d5cd19326c399727f453486bdc7b85202ecd41f5fef58b5078e887ec5b91f4d39ddbe24e2233747a1e65e6737b58c0850e148f7e92cb21e947efb518882acf619244aca195b5dd44fe507cf4f7b1aa9386b7904b01769ffa8211b3ea6ab32f2c85a4d03c6266632b602c655f1dc01662d283f3fb700a734f14c580bfc7a5d2228ff1a4c7761708d9322f2e000001bc00000090c6d6caffce822bbedfa8b303833ce3bb9b0b3a79bfbfcbfd3b563f02298c592c4c7216a38ea06d6e9a1a75b8ce59730cc56c8488246b4d5261647058936277ea7ca9ec6df4bfaab432e580fb3ee6ad005ae36548d2fb1e7edc2f1228431bb1acc1e3210b4f8d5b68647dbee0bb7924fe2727a6669cfc46cb82fcdc1c77e3a0de61eb3f0a7da43811a067fcc07a5303300000009014237a82b5c531c5b22ebeb4f7af578d7998537fef0b53ee2fd725d07550da956a9ea3ffbdd09144a539b5fe593a138d39e93551ff290cc899b250940749ec14f745945d067438975a090af541be7ca68c7acbec35e623263cabd630ebd11d6caeefaa085a44d44810e70f8d6a631a8207ea4aff253f47613ca512bf04b63bad9dcc861f61d81ed0d7a88fe0747c6027000000908e8e062b17d6f040bf583520ad0d92e257880d51cd470ca10fd94d0e03815062d26f1169b1ebaaf9b5dde8a5638abb39043bd0e31d0939415ed88682e4b1d0e434f879f11542360439caf4db74632afce8378f2faf1316e528cf3a64d7e7e3a5fcf322938c266d48181fb71b956c6f66353469056cf26e8fd84c3ae60f72ffcf1f9deb3363b93f5ba1dda798c93f4ff5000001bc000000908cfb998afce6a15aa54c86f27370d5fa09e60944ffd9ba0cad5da227e2df3e678226f78094eac382cd0cfa9fa7dadf181c190002fde911048c253e2f05e8267af3129dd8be984d00e9ef152762ef161f93fc72ef1ac9bd0de640f6930a88f1fcfa9ad5bf5032505cb5c90ad5a7b22cd0b2c8d9af4f224955b28bed5900a852c9418d94f1a1e968afcae8d489895996a800000090579b53b18d0bd8e4db71db680804471f263423a4c57a5807696dae3a7d751e6dd8c1340ed4e0bb63e6e45b1fa36519d89f164f4ffaa41d391d4adeee0d02273595d6cfd41d0869677ddfe43cf8d4fc6e711d983bf9064485083edf3dfac78ceddd10474c1e07234f04ecbc50cb1bac7c30248d5bd9efae2bd72baad5d108f98de87b6ec6414d7581031cca18a7a1f88d000000903df50823b273945486cac84eae73e266121418fc5756bfc8ea6809d69410c76daee250d34b31a846e74ed1e9b87ad3dcfe92600d5bf24d620319554a8c67b0c2a76816d6bbd0ad08d83a89b121a4209e751b3e1d2ab0ad916fa673ab94ae1ce7c5367d77a7e5094a96e8c6d85575e34ebcf5ccddded1e82e329b0f5d0606f0010754789ee4e8be98adc2578c242d3eb2000001bc00000090fb7cd18168f47d0ddf433e52f0ea9f4f1d391b476226feae60b6258e7e9203983154c17120ef162fb83d858ed5d89acfb48b5b42ceeaf4cc45a3a2774814a2bc080b2a5d139aa7d33d54730e7c6b72b8c81475d99e430e914d609adba55d572d9fd9a2d0d3b55765aa106faac79fc50ade6e09d2fb18631a6e5028dd30a03fd78756608d3390e38358345734691e130400000090f354980488624491d43905213b4f502c0e0cf43c88bc840c0cbf1f103e74c47c3bd8bebf1453aee92c4ea82615fb9f4a50aad10dc69dabdb8ec7c8e518f55628f48a22867302d0aedbe01330ef940b87fcbe1522609073955a1f4e53b580d9fb5947abd430f3ada138bd38bfe014bf017d257cc3cdae7ad84837ad0aac1b8fc85d7f619247c58c4b84d96bb47d61447d000000909a34e935d1b48a64b74a4b7a130471c3197ca84342093096b7d7a5e57bf4aa9d89f286b4a27619861589c15f9ff4ac6277093e2bdb8f1418ce45c88b1b94d69c8fa5194ad9bbcf79de7ae6596d0f81fd1549667d7a97d3ad96e21fa687964b2d29ea50b7c0fff8bed8e3f5df7c0b7528130bc9443be97e2cd56bfae66b9f471572223673a7027b9df9c8275b7bf3cdcc000001bc0000009048deb9e0401c49c61deeea50914ae71c267a5056dcce4406b02478aead4616303a6d36685b3d23d995d2a3c31dfcd58a4925ae67251b98bc7a08521bbc80b850b7c268e8b796d8e4436e156fd1d9f906c508132999f3928da46a5829c3ef8636ce949c5ea263c6e0a23427bea71a8fe5cc35344d0e9d61b2891de94fd71fe97e23fdd6a1f28937cd6a4efa1e3282614900000090ef21002e4193e387593866f9568792747b9dc5353764bb3cfc9691068cd7ec63509277384c3e12130782690775bfd7598b30130d4797603c432bc9b5bea6a27f0c69b05374e731c220ff281450b2d4e126716e2bee5436d160b2f6731bf35c900639a6d7e65330b75439d3c7c6245f876e2950cc7fa970ad9ff16ebb6a0898525dbff3e00564cd209f260c2bc4f694910000009011f59e99b692f90fb8cb1a1724ea0cc8585330f95afbc3393e707c4d2a7703cd6ab9d975e56c7c1d10863b3b139958722f4af95cfda6159bca44175d9b5c6ac481caa2a86c6770abb6d6925a00213cea8455e87213b575da93ca24ee48e627145db559c962519911b1afb7a47af5ba0460a4a97ba00bd4bfe9f52f194c5e8ecbf6427cc4560227224f38c07b7e59a4ba000001bc00000090375f55f2a7cb6057626722e28363fabd45f30ea1babf12ffe3184303eb5788010d104812cc4db5d7d18114007d9f4314e64fd02acba6c5bbf83ccf2f35446b21a1e7955c55e6859fefe6f71f2e16ddf4ec1ed3c5178daf2c9e5613c3e9aae3201946fcdbeb0e8719b01a18a25905cca216f034c4f94ace0ee4bcfb8bf0b40ff0cdcd8f1238384e28df63c1ba65f91b540000009025e941529ddcb4ac5e93de566fac91cad1baa6dae9cda86477c95ce97c7fa81b6a3db36b657711ea0a1a7669a87ebb593bae554b66a058af1a21ecf1b2c5aecfa191f54733a32480d29a1ba026b6fc90b0ee4e718738ee68c2b34e774116f045f1138ed8c99ba88b98affe4806efa98b32435d368c6f34dde1fa09715624ac0611faf13adc4a8a6eef04646897ce61af00000090005b88cd11607a66bd9fdf06b8023ae16072df1d1a7d223a6d7d37049dbdd417043e11e1736c01af9098b6e30ae6e59e1882deae1521bd41b011c3f18fbb569d20e9af66508f743059537843cd1088ca526dc1f174a8018e467dc19af5cfb12d20d09664ecfafa7b1acf78c1396f0dd20f606f27813b004b81f347c313fcc77ceb5fc33758c4042e3bba26e6b4c9ac79000001bc0000009011e23f2328a3cdcb9652a6dc95eca90584fe46f92b70362a33f8374faf6c26f54650df91601eaf32e44ef840c5c3e0e5d02e51198bd57920a5c8b22aa96288c430ca5c462b8aab462c9222c36adb884bf5149978707ef563dad2ab59041332b569c79cf44e915c5aca22b75ae7218e996bc17d8409b0cfe5ea948fa2f95162b00069e3046acb65d5e245756709742b6f00000090193875fe2d757b5c35f529652a27b43f96d9866935e92806baa042413da52f6785a97ffa06741bf7f3a52031e8f67bc4ce40528f304d1e9ef6efa19575100fbe49f2147d7e0ec69f0f232c239fcce61a50addc45875278d5ac24e389521fca0c6a49eda5f3ca7f5cc7f1f9fad15332c9e6548f77cc0fbafb72413f6af7a44365b67dcef769105d6fb99e6acfcb8ec3d80000009060f4380337b2cf5647dc40b9d4d198ab5c8ce6317e4ca71b6b120612539ddc0de66f6b5a42257146ce190103849efa4dbc68903f1a32164258c81ce43caf8b53aa73519fc442f2f93cb9494fa19ba62817d116704233bce8a761a13221cfbead9bba235b6567045072eda0f4da79a5abdc640ca003cc218d3ad51fb6f5699ac03207e37e457d997288c90e043a4b49e1000001bc0000009027ec222ad55c14e1b629cba9e7b024ad8b045150f409c624f1b4b5772994e2f847ed4c6fc9ba0b887fdb370dfab6bc9a029a882d513e848d0a000e6a21d392081159e910e1824ca8336326c6fd32e62ee4fb91d3bcd1ea95316ccfd428235da330cf4e74853790219bc1d868b696ce9f289d416dd11908e410c00a7a2b0ae38ac6aee983af156f7ab445944bf3c78e8b00000090c7b511f4186d7293c83edbc937e81e2068ccb3253c10908e1dbb877098d3312ff806bf187b9521032fc91802a275c546310d3ede01710cadb7fef5c5ca3f188c900120b753a91fe69a4bd6ee2c39bed71c7ca4d6d5718ca20a3c83795545b169608e6248ef7b16e892c08bdfb2e261f85863b80f65540b1a68f1fd872fd2a9012f645a9177f656ab4bf297b5340983a500000090acba85789c0bd050a43cbd36f5b1c1e427f898fcee8c905e59e58b0218f7f652f7f947cfb947b52336439ec95ff4472481c871ca400dca0b90bd36ca63cc72e884736cae8a5ea449e383aab49c4cd6fab9c185f5b03b2bb9995436b1736674b1a1f8a9f4eb1102ad1fc406db1efa35e57d04cb2b69521ca599c548a3545805d44835b5dfde8b9039207128eba0be564d00000e00000001bc000000905bd444a8cba9ec75f404b87314bfd6020f57de5ccbe53d5fc09d4ab7d0523bad932cc48f4547c19d6fb6e5dfd506051992fcaf21fdfda48c0cbc1bac1a0db7bec9bebf7c40350a275594cdb6c0f5c94478f2d8ddb378ee5553051ba1014b4ef11c72588a4add965476d0da2f6d92e8c0745301d16b48600e1818114f9b02d82c8fe9ea5faf6a6033431267feb6788e3800000090c2cbadc76ca5c5af8dcf7288d4ef510b6114c120dfc23294df68240116291024a307f69ef01a033c11adac13172bbcbd83499538f279d4e6979184bdb3e9a71dce200df699cad843448318fc765621c6e762e8115488774101f7e3fab9e5c3119cea5b3c016fa19e2fd7686d7ab0bae4a036236ffb0cd9a1de8adeb1b624e150e917033d9a6aaaf2a93533d70007bdfc00000090328f9d32f22edfebdb68b5243666e71fe47a0739ad54826a2c9022076c8ca9f6e33deb528608d088e3e1d9a4222f1155caad9f3c4411bd779fe4e48a416cd6b30dd4dc5af43781989d1677d3c9422ee5687fef2d3f2de01d28800086c3cdddd4fab892dfc81ebdc8538f69c21137b4edb06e3b57ae6e75b684510c05225cfaf9e3ca6523fa821c3769d41a21fde5552e000001bc00000090fcbe73df4cbe4ce643bbee6919bf8ce36bb0083e91ddf443c4ee83374ceb33a79ff29e7155da16a7e1a4cc387b97981d6c38ca137a53c59b9e953d528df5e966da7e28dd7aa35b22cd75f86a920715f03dca7ef0f24bcb605e4be105a9b5d5f3fbe87f5d8bc8536011782da77732815fd863e08348fe2eb2c550f8bec4410d3850a79fd9cd2e37a1d244ba3184a99d2800000090859704b25ed494302d05adc7012684828a21a45b8aeb93df0a2d1ea91e312d66a2832a040bd2bcbbebaae7cb9dd3aa225d094a55977583142576a401ad449ed0593e7d9fd1cbc37ce036af276f565a038b63875d8090ccff076ac7495b4ff9631e86a6a0cdfa74e2f677750b1a1c280fb6ee6ff516163491aa744aad3a1c487cdab16dfdaec960b66ca812785ccf1f2a000000905726064d12c28f46856076461dd95fef270926eacd55074d64c998310a4798b699e8c864d863fa24c164036b379429ba81e7b9b52afda13d16cfcb231ff6e36812b16064122a163cd7bf935ca76ed6e275cd0e355aa41880a54c6f4fc9089e66837f477a5a0dda745ac9196f94fc8e1ec36eb8ea932eb6b4d737d6d005d0b8e3f299a2c02ac881de574900c89eea6339000001bc00000090bfee39eff3bae13492bf9ce976a56c09f166dbcd46ece93ab636914d02a7392596a2593c973fc5329d8dd03aed494ce37ba3a9a8d44108be1ff4238c414f2d8b9de63fc3adbe43edfc0bfcd0b2c85b9752eb49a51f36b995d20a3f7d80b35c30097ccc4fbe50cc4bf627e51771b5abcd43806c5d9dbce141c8c002bd56ea73d3cdfb523d1b6195ecdab57b427096227b000000900296ad69087cf87ef862839d9581a851861d600fa3e1e0dd4eea3cbebb18b868000356deed62fc7f1645f1a0b088aa169164b7f93b1ebd2e82485a677b57b68166d7896b4c65fb4808eceecca609f95546159f1d2b4ebf30862f18673f1d0248025bac6c928438e32c3b087abe01b8e827c811178866b40b4e3c14f8d1b6e89949ba757df7d18c21dd7a71fc5d4efe13000000900d33920df26da7d1d6b36377566e86e40a8015ecf91f88630ed533afafbf02a9aae149987f96ebb264eb582b1fb1aef2dbcaccc9efd52801c7d4d091b4c4a6e1378ddadfee155b09597a2ab3b1901780f0e606f6e1ffb02decffbc61f6ed141c282122b58b6ebd5807f3005d365eca15d2ce2c6ee04e53f0279af43b59017f3a5521ac893649bbf5e44cb9831879a7be000001bc00000090cc65151f7ad0a4bf1b0355424b24cc6eedba544cd71860bdd03a6855fc8242a77f21ccd3ee05d414c7cc8c8ac06108cb6102327bdb2a50dba83060c70b6cbcb31c817da3bade2a008761a0055374adbb54e0b549fe8ef201b83064196cd04bc0df1500b86ba47caf85b8bf64c01e93f27290ee8f0cfb6a5a53d9e6e2e092642df9d33d21139cceda22b76cfcdc0bbd3900000090378d2ba4c3e4b6d7f41c3e40cf644800141153017fb22ed45e0afc3f3f33f26da0a42071f04441b98a1af1bc41fc67f46ec17497f1ae5e10850c11452a284825a40d124958218ed6ab40f0babea0a827dba3a9e6efe5a9ebabbe59a1a3d64cad6ff4d706d2cad4c3f18964bc3afa143e8eff0b6469324ab2f417a4c1508b917a3e87c2f826a2ae81964f3a2c5af72d81000000906cbd3d7a20ae20388791281bbf4e67beaa96f258182d43044da9f1ebd6887b1b72da20bf46b67a23096b3f8509177b9c8dc77556cdbcc916b1ed02737ce1a8b34d3e74058494773916a1d1ab7f6658b09bf396ed9a2e70ec021178f9078dd57ca020c7a36b4e75548658ac642118f8c74062ad1169c47c2ff445433a50ff23623bae3a52b5a614fab34d12eaa314bb26000001bc000000906ea8321846cadbbbd83f29e437e2f68ed651e32a80b91c23fa9aff1116e3f7d084d238933ee634c640a369880b6eb7e1df8dd518e82546a53657a7aaf8f8e7ee8fdabb094a22378a14da612310e7dd6502aa80f268fd9bc57345c0d62c8b5b3dbd20c652445f67444074c186de015ef73221e6e865007e72082b14e9a6b03f76f5eeadfcbb705c1b03d6b983e8be0a28000000905219224f11bb74c2905c836b29a468148d41fe096400b1c4a5622c876d4fad2c161a13fbadb299cb0274f3ebb24f19f994a0fc82dff8523587cd1860b97bdf73a8a389a8b933a4711ab03bd77b28caabfdcfd2ab79cc27fe4e347d62b7acc92d385c206e4964e6252c4dffe60cc0bc6d9d0e265237941efe40116c02ed8dfa3805a63b6c998936e0a6e3d220bad3ee1300000090d07856ff8c6054302a935ee722eaf3cb1b9ec87e1c99ec4d5f05b82035d25abd2919636567dff299030db4df879a1e3da982747382e803240281fd86455d3e0ccec709c94a3226ad404ba79d6548a729a9c9ad21e1070e32154c4bb5665ea38ba1d7cf28598b3c115d50283af0a9f83da97a6571fabad795c7da119c7332ce25f686ad84f40b3ef6b08328bdb8e4733e000001bc00000090d3f985d89d0e61756a2139c09fd5502a712bf54663733ddf69cb1d3101d85c63924c625b12cd3bd36ada36864bc4443899d3c0735a5c2a29f86e8c8673952bd937cc39a6101ea05ea7e29d5ca473c8c05e3d3e37a9cb63fe19a38b2143155a2412b7feb1edd44b9686dc1179e04157e5965199f3d597190cc205e17333c995c32d5875f222e11759ccaca03613f713410000009094984b569bb94bad9099f29b90ef394dd454e2af7e3407a242f48bd08cd3fc111f49fbfc8093676a0a0f958a44b532e771e676fbcec59a5d965a98681e5460260ccd9ff9afa3638d92e4405d1f26776855ed9458d250c2854550f2e7bc3b2a7b1d2e49b3f08100ce724b420bd2f3de6a8078622e00fc4616bbf50da777426ddb2c47dc704cfcd6deb695156ca4a60e8000000090beee2b163d5d4a655689e8b0c30c8510d5c9e0fc68df0c94e9fa90d88a01d433523495024f2bf686d72323d34d8559aaacc1806e7bcb45b51cf697e3dce63ea0443807ae95619d8cbecc598e60b40ea544e93bb21b8d5862942774ebcf4ab1f56f42db3b75e3bcdc61e2c601b73286789f49ffd8b16788ce30dcd556aa35cbf32e1f628ef27cda313b1b779afa78372b000001bc00000090ca71834083c2c4a2c84e8192dec2d6f869bb192cbf4abd9a7f4fcd832d27875153abb7cc2da5eb8ccb6915b96a294cce6e99ba7632ef6757d7008b0bf0958d11e1bd49796e191a0c0c8417b4923da5c0779aaaed17ad3b4fad14065fd4203d73375e945b7792260c28893e89e3474970f80a2d5ea0f524f3efb5ffa8c2b895cbd32c5a77595c55d0b02133b97519f24600000090de3733625aa8d0451bbf8ceb0be90358fddd5863068f18505e05f9620da1bff7285b56740cf001d503bc05ff9e47c0769b45c5066c9f521cba91852d64db3748354ab1e9d12dac0284d0b63545918f00f4f1bc14c98760b9f02a666ae4721eb7b99254922bdc980ee12674075da138d68b385df945e3695c337af5ca590026d81bc5170366d9e1ee6e1a6b964782859a00000090b46b4bc458ff824b02d652b9e0e7d785589131d3359e69893c85b1606bf776de78b58044d0beb789dfaeb3104c8321ea76369c5db074097f126f8b0078dffd3ce005eb18a09cf43ac4f6f1f38dd58d1bdb1dd0dae627b5528d10adee3e1a822ca5c1aa8489c0f0e8ecdec41ad817ee12d534a241f7a6a13bb0ebe6a0e6d6349939669d5bdb654bb8f69d111a941af156000001bc0000009047099475fc1633753f715412724344352f1e4e82ce39b92b0d12b252413edde0e7c95f8b3b5ebbde7a57ba3da250bf51b2cc9c8c3f29e8b50d29ea0f31015879ca8bd47f6e092955c3c45f028b68350001831603834529b9e9625fee1b2a212e006b853c3ea4fc9124da15fd610a40aecc48728f1be8e8db55a32b26de66d1790e404c95710c76baeba3e5912a53768e00000090bcabda0ca6aea4f9d32bd42a74401684c5f46a968e48f394fb82b06ed1a36b0024f41ed47e948ddd2f96f0f9e2df0e7e61f67440a2b6e41c59059f2f563f27b21b4a7960c6f2238faf8d3bae1b31c1ed8974932b1a4ecec12e11772265bd9ded7b6751cdb0ace6d0db1055121a81f8f39b86d4e265c2bd82aa63fa52135b18c7089105d261e8a7147712b67a4f41cdcb00000090e02c17c4eacd5cab85df9268d835e0b3c1210fb114be217c839a764f1ac161bbdc2774a4fad9803a4c99315afc07b84c3796c7420f48c4a69ae5cbb3fd637a34ffb4ad7828fb81961d3bfcf6f52530d0b53f35adc9f88c96612123a23a011adb9c2b8e9acb53ee608ff642307de4ae094c859489acfe3c1d85119c4a786a1af5a3efafe2685aa4747cc63fdc84e9146500000e00000001bc00000090538a0441399b0eb4b37558ecf90d9bf9d67db754fda7d60fc3deb3c4e767bcc087f8c11ee7ee94353bb1a943dc8d148f97c3172f61121fab91a333f97129592b891c45de2c185d57c1548022605fed55bf47dbbe5470fbb7fdebc757800c35880c5ae70e372cc46272caceb0003fd6a46126a790e8d65baf954669f71bd817bc0e8c9b36e735963afcd3256ef4505b2400000090bcabc8093838df793a13c3cf44a1a70fff0569e2b1356a210fd55913b23068d1a98ea92dd3df55bd2755d97b09887edf8860b310a675eeec0ac1869aae6289c3e49560b8f4eb583bf40f6e21654a6b80e7af429fc7f038d95ecffba457ed949bbb7995158b2061f986643795c68b7967facb2cd028641daedf89779cf3fdfc90d12865fb851a5cf4eeabc7911d3d8f4a000000901f9dadab38f2ad1c8c0bf1b02c9bf4d584069a3b54a92ce07141c19e7bc975ea943719224e6f93fe69dfd882b6ecc94ec80a1da4189443cb04636e92f09f88adacea1ba91c31cfb9b7474ff1d58d63652f522adf727c82127393617c4b1530d8c961d41e378e3f5f266efc1bb0b7455d75148d6c79f98c01a5d5dafd3ba03f2639b4b45678ef60587bea187af77a11bb000001bc000000903442c44f4115857ef498fa03b90666e4b043398d13f0c2523a31c4ad92bebb6b6a3826f7f9158e1610939d54d9a3a041aa38d1192bf0dc797710a83799f48e470deeca42989c44b7622c4b99ad4922673f9ca98815da551a3d3e882643bdfb9cf0987abc462b859b433c1c7e1c9e96b4b4961302654f31326b8c6ab0b751d6f267c2adcad54b9058cc6fd5e9c8b70cd4000000906d69f738576233ec2328d6bd1ee76e6b40dab296dc3b214e02c6418e007ea20470acd84bd70c7ba5c24cb4360c8641f399a5b928077b3d11ceaaa73c11f7f37bd246208926b9c1b0e55abe71413511c4d0f04e811246a32542f7df6ac2a53254f1fcb9dd9716d85697cfe7afb069c65e9c03247da7195e9d39cc81f3dbc1a5b78e989c9e6d8956376e65a68b15be17340000009076c552a5d849a23c028a8df9458c56ef39d43d4e88b83cc86a7c5f93937b9c4db4f58496390d315c983284b9b68b5a546ec81ad4276d487ad1327ceb7b1f028887b1a7dd91fc6df07232e8371d78770a860f4890bef4d66018bc2dd5cf19c19da34b5efceb81804574f18011c9d52373d6d4996811bfa4ac2f947d90f5dcff96c9a8a4a407950c1de8c24bfff838971c000001bc0000009020c2f2ce0288f6e482241ead06ee7eb7b9f7d75f81c08b0da7b8d74fcf71c226add5b6db17fc64cdebd52bace97d20a655a05db3e73cfe16a0b88f11c049bb4a3b84ba4b50ccd39a9bde94edda541f5b44fda6a243468e74b9207fcd9d0385417891fe89f8c98a7ff77709e225c2039b6669e9879b5f595f52b194051ffa1a28f79973c4766e14da62a94b96483ac44200000090899898e94b501a777a9c12fb71a41e7697903ee2467966a5fe4ebb3024763e3d1ec29fee30c778929cb1a8a7265bd94d2dc67b1f6f6c8fea74ec2218ebcfaafb86f31426be18238178d23a165e671a30f3cb1b583f31555cb2be7e939aaa3a5b730c719fc73baccb83f69c07b26787d0107c7576dddc9dbd9d5fc7d0877f5150b27379c1d0b957180db2f528caadbc870000009057a22904be7a901c8cc3780b0277abfd02cc690199f208162df307f4168800bfbf2d997b9164cd2094a905d0d990cba8bebdfc6b4f30f740f811b44d203454d4364376c90912dc515f7752494649674edce66dc97a66dbb635ae57019ec8c46279e5373b6acf992a1a49edfdc3975f89d3185ad1b2de46ec88647230df94679cc356cb7abadd931aa6f294e34c3eed16000001bc0000009025609637221fe1306882993148602ceb784483a1151fed8e832fa18354016cdf71254410e38ac04184851ef76e6fcc5aba4e9a6c9e2638e41c7c5f8cee1b13cd3b0d17035b91ad04c63ee8a196ba537c26145c76a657e3d58ce9fed1d815a923033f7f3b4e866678d5060c1fbe338741b642162e9b7b8f9c47652521db9d99936c4d1ad38ee22f40bee9bc0f9ada6616000000901cdc562761ae6a5df3735ae6520eb47449dbd832f71ed2fc601aba142d20e1db7af47e209133de4a4874478df07071a4e0e373f4c2a61f0ef9036ac6b9f7dc2ca68a9b9d42d95caf623a8d1cb579b14e8b7d3fb8364f41bb945b63d933917fccec8de9746d5a295470a0c72ae94f47ed1a19445e70fdb586d9e6e7040bda1392fe9f3824c366512191767ddb708db86000000090f1a3c9017a4f4e312db8be2df5c2ffc914976a896d42ed510c47de5e437d6a4d7c61f893b78170690fad42b3f785fc77aec0a3cfc350fa0fb8c2d167b83f3d6fcb68e198633ab4538cd326e50c60d09f11cc14ddbf310e66c54021708c305bf57fa089950e565ae42169fbae159a661e68e641373df3cd38f455f3b8ab7d502d8187451fbe918b56a7c826e97ece64f4000001bc000000904576be0ee1fb3989c2b50c3c12eb035478869d0070c9522062b4d729485a9d6ca310cb28835f7611f4c44e8a92bbce8a7b52e229ce919a836b1942920d895205469b78da41f4862da83b05228c9728fcb1865b5b24002d2cbd16df45312f7dd1321943f1f7bb6aa432bdb84462069bcfc58650c1b4bdafd3992b5ba65657965656128fe027ce897b2051c9c697a1bcfd000000906898e921b3171d11bfa8902b63cfe050b43129c7395b6408415a18ad43c9543ed00cf3932f6023e999bc2ecb19853b3f20cf8cd5dbbe8514d9ea535dc4767cc9c9e88ff5bc664641e6fcf7d60b4cda224238e457073e702b2aa03c6d3dbb3ee797493a3afb2d079389e1548b761aa78929c4654c1dc1260d1db3cc989eab174cf2639838f844efcc4f648ed6b1c635f700000090fc9483293459d113eee0962ddc86156fd3cd45d05a0c16e07b4c0f3bfa660e3b069664717211a3cc3682945993548caa5b22df049a2f86238b3118ea877ecf9829afffab51135dfb66651aca3346892221585fe7b4c05b112fdfeee3134f6e8d3c7205e0de0a7d11fd4ba79ee612e840d6f859a4e3c746d122efd2e80755b46e4acf8c38d23f9271e101865e1ed823fb000001bc00000090c3cf4166479dca196f8f0246666941e3159f4e630223144a4e7a5a3f9f275c365e9191c212e2463a159e9de3155255f0f9c3b26c82f97c1d82601be3c51eb33752533cedfa1df5a434d37565da7aeb49f6fce23d4419aa3dc6c29478260e4254119cd7fbc9734d2ad321ab505d3b3c81eaa6e991c9a101e7250d71acadce5669281a3586b524e89064a42ebbcbf9f65400000090f32756a1d3fac1cfd947619de20dd66cb5a3bab4a4b1735d81e0b70deef25533bd6183cbe0b46ce35a4fa3ebc01b62302bb65d2a6db20233b500577296657e24768a1837c1eacd099834e69d3f878ce837dd31a7b8a8ed78e624a1810a7aba36f6ddb23c411b2ee5034a366c18bfaf3bf8e32e6d38361c9150f3c008825b8be8a3e5d177c80b2450c4feffbf29c8bce500000090c815b26c6e93de18db7c07eefdd72c77fc04eede275c87032768403812324fb8627805521f427e1fe5f881739df67d2f94fe358fddc4a971495b8ab18dfc8bcd83acc0117b65e15ece82916332c400424b9df1a8c6faf744d3d858cc63d5748ef8e927b7cb58c0129426a11afea0bb752eba612fb3f3bc43b1a2c627e7eeaf83e67f54683a0b4886990b67e07ce112b2000001bc0000009047403c11095d5b37db843ee4def2f8314b6f35746d30dbcf7095a8f5bb372ba1f385c21a8261210f8d0ac4c574f48bcff6f6f328bd938542b88fed261a7bde161f45913a788d2e418e9c8e432edfedd0147966d3bcaf0b98d0df08552403e92fffc01a0508721e1ad14df4306b4308dee4de010f603093bab5faed87c6cf60ba3171a00856f2ac3b06b699f7bfeb90f700000090af5b01631cce3055d7a9453decba284c3fdc06b0b71d98331cda285eea5206b3d2849aa349f8964e41cdeee6e5033639273d9a84fd20d695347a217822773890dced57219cc9acd1c866875d58939b7436db0eaaae1e44ed46ec19aa52c5f07139084a2ab1750e25c4ba379c4e683c08dbd954495bf6290e6edc61fdbe268142910e82c6aef3873100e3e7f40c4e856b00000090eb59cea068d5d6f4ee1d8115de3caa9ac0bc1ef647dab13a897080ee608c4ac251f4ef3fef30c12a7ac13eb02de4f2a37027b245dd9f2ea62465039a847c84c9ce2058930d1fa6d573866a018abf06350b09f9ad6e6fb0934122c64e7edc06690432f62faf63bf11f2881d3026ac9bd17d2143f7bd55cecd872e6fc98494f20c9f1d3c6436a4eff1baac096c0c5c882e000001bc000000907a519a60fe33f4b9382b5a67a538ebf6f53574dc1b2226f682bb87090ed48de9760c9b3e96ff6c87a8d2f2bcf4eabd7ae4d7f7859b10b3490561221bda35379a6196f07f7eec1be17be8abf819fa543655da0b0e224a2206197a464dfc7f0f812f194256ec8197aa776989f7f6b373a3b7a9fdbd40f85b203d63ea04bb972dbfcd15e5e7f1107e6270662260771da70e00000090bfa04272dd685cca97b73fe43df6f13aec32a46040e7a6495aed0c841aaeb495dc9428024abb90cb8309a9bc381a08428cbce5642ba0c18ba4cebf593102a05c62dffebddd38699b7d0f61d9f0114e3d7c713d65175a0aadbd4ad218a71bd8c9c9822dd7aeb4fc66994e2d31402cf81462f3ca7dc5df16a2dd15326f212096b22b30f2e8634327453802da76ac65615c00000090a479c98778c84ca2d9882cc42ef49d8441e855a2f54a43e045ded34978d82b0b1b0cd89ae968660d697b5873cb35dc91d8f463c2607d6f8848ce86a8c9eb3d3acb36d049915591056ab1b79aae00a5f568dcc3c48ae34c054ecdbfed0e96eae51e734386b262e62de543bc05d56251cad682b889103e58a536508821f847033a321142d8933ecc097dcefc5430fadb3a00000e00000001bc00000090b7968a955d2dfccf014999255b9ca6e954d7caf9a303bef2814245542a14bbfe846c946b03c54c945928d7c18b4a756d3636260d94f5d69ef6f93c523b2d80e2dfa6212f9fa3c82b083f3cc5d0a6a500837ecaff099978ac1229a823df5b11facb2eef513c1b277d704e51c28551814916d644e8818d8f971e1d80f2f735644be615e57885d8568b3efc13b4a9a1fb0a00000090a10b4b7824af15267906d782f4904863afba1c3d1ed581e99e32c6c493a7846e6f846c84286bf333ed45ae38e0510284cb164ccabd2fe49d5791a710b94c8df61b337fe569991dc692314e8eca72af12107c2d75080debacdbc1318c0a8c40b6cb600c366a733fecc7f76543c4dd73ca4b237ab3b176d98537532fe0382ffddf4e4426f5227b0a4ac756c9f6badb234e0000009085c8b49b001dd30b7018b2a4a90e8485c200113f9c3c7867cdf5a4cbfc8490f3b34a056c35be01b576f39d69a86e7f60c2f6f8f1ee445b96a4449114ea6ed1e8d03fbc50bfc658c43e8d71dde864b506380ad15dd48cf99dc7fd3c82f3f1e48bc4409ae2ace69c4350fb1235f7e445b1f41e96477784299439dd984b19d8bba4489aeb66862d543ff340073d046c80f9000001bc000000908e9cc39a7bdf0721646ba3b0233c7b8631903679258eb14f27966ecc188d74c92fb176aed4fb55e0022bc417a1976da6b1e9ef8a26a9ebacc234bd030c9f930e09fadb0e5989f964ac2db2e8c08fea5ce219ee1c757de3b3305d537c5acbdb7164e6c0c5bdea99ca6e05751c3d6c8ca4abfd29f3027cd21467dda03574adf18be31acfb55227f08291fc7ff94aafa82900000090d7ea79f2a79ac7b7199608412594e9c39d6d200c865056541c65d00218430bb6eecca91752dd462ed04bf2337c17763fc18326195672e7664138f28f44afe7bd8ee5e36e5237fe193ef4c82c8d0c608bc078dec1bf9a6e693113e6d91e261c579c66478ac5a1596ccb91ed884dcf88ff8b8ed010c46d26b560d54b96eddd6b4f435b2149aca948f24d9403096efc7d7000000090cf657fdb9a0dd2b397c3c6f86cabe903860f881fa3d787e00179173c8061a7a903389c4f0ff1bac518ddccf514eefc9814f49f54ab22f25cf0bafeb92589108ffcd6c35ead1950640949e8f7025f5e3e669dd8f298498655d4f22d4908c40f6a1c9d02ff790d397440b7d0e0e64b2eb03b52be821ee39dad03788d613b876fca3053ce1fbbf61588be94342740011cf6000001bc00000090db066dd99708e02662124ea437c46cad412577c6b6e94d535f1bc556be4d2215c19428926ffe706f811af6eb239dacdcfe6f2b00c14c70ccbf2548cd3eb3c59ee030c1a0b3f02e952e3e370f39a3407711a393569c9905fe0be6048cada151dd5fb1337d334b5dc1c2b018eaf892d3edf265f5c22e45dcf843b0647159f4dcde84bdf31133bbf8b7c692b7d24c1a063d000000907bd9616b869597f8c331a4112d51429c804370966e033be8135121beb7a4dfc21ecdc37facdc01ecc6d1db6e9435ecb4833f197b6577403d57d2c09f511e496da938ea4898bbbeefcf5c776112961888d2aab79a214ed7e45d34a7699ad5523e02ae32c96a2a49c0cc5da2581e81ccf55439f4f5d0e52f5542c86a65dc8c30aaa326972fc852c514e57877fc1632a10100000090db2a0f48ef0ee0a981673c0fc77b364c4d5b02498e71853c6806e66f215613b266da6f2c3295f6a61099d72f94ceef9a0ceff8464c2b9b106bf36bf6ccfd512f56e7d026e7f680ae933978974704a03b27a1ce316c14c50bd191380b7ae6e5eed923737aebcf01092047f81ecd47c9441536c5a4579dbb32a1fa3c2d5758014750efec3a27c52ea30b68f5d9f4850e2e000001bc0000009028a58a7daf03e720795deb55e471a44b048b694f524903dd1aadc6779f6b286caaafcfd0b95d213eff8ebdf24a227ee8a2503d9c02ee079bee47cc032af93c1f513da5c754599e5c14a53cce674e1e37980840e2cbfa446d7c8e00ac0797cc6973c723f3e7e1e4f05eeb27463c1cac066661b441c36d43d223effcbb1a7e16eb9a616c23efa8643703a519fce0e3dec900000090e12682942a9cb9d8d97e0d8542d4b3eb2f61e1c6b18d15dbe382472cb8eb601c2191b239f98b6de85ff2d96b53c5158e33dd6d195bf4837dc481d5c068136ec4d31923c522342c81ff3fac8721a1da2e83d81be1e3d657a9d6ea592a634ffd4385226199dadadd8af222616ddd36b8a58b6b014d8659e76e99a4447a1773869f57c4ff69075ccddfa46de2db7673102a0000009014acf34b96562f756f3a34419fc01eefef5123364d56c35c3226813f56b7868e300d8c299ef09be1b08b38a85f759bce2b14fffcf6892f37d18c54ba9516a85fe9ed9de7293d7e7dd0a8d79ec9b069faf44f250c4c5f748b396901228c7bafcd7c89059fc0b330b37a2b9be7c4805c50c8cdf439e0f6b8fb7e8706ed29940bf7098edde8b3f427405865d2a6dedbd040000001bc0000009076e1ab6c68777495c22513e219f4a9ef4ddff594c95ad42337177a1fb4842ee0494168c135904a0820e65e95ef99548b80dacc87fe0ad7a58c67076640d3075e9f9f1cfcecce95b7d1178fbf6ae8662ac3e09c6470fb6ee972e1af3833309657eb02185552813b4231fb0dfef0046567243efe8bf958502aa87c2b08463f71ca4194bff89dd4b29b7e039784145d5e430000009041331715e1cb585124b0dc39418125664fe058a973203758019fa2ef0ea59c7bd5699c0d072e61cfade043c16f98fe730b4c8b62c66aeca37cb5914337462d7a8865ea805c3a989f184a154b8cb49305d9b15d98703e9bc55b33d3fdf0ac4cf476ed841c65a6372744f4edd93b550e48b49c3acbeea01235449807c8bd5edc26433d94016700d07365769ecdce72bf4400000090c362e0f4934778f4585405e6402fb9de60210e5f1209f942cf428510da7f03774f0121a7259defeb3f1ff6895b5dabb8fa7dcf1c7e1c8f157c01f00f62968a872d0b1d5d072d37aa0bd644d63489d0fedb5eed785b537c852f81b1def1b4dc788eb6d8255e396854a06fbabe3cc4482301c38923ad0be4f9696227eb3f5d83e99f554f1dbd79d1b64a50341a85a73740000001bc00000090a6e13c2eb058853e0865e0d942ec7d63cbca97e6aace8d62edb8429a5a2cbd2a2ce68508e01326c60881c584e71f4eecce64d2d2ad804c62ca47c5a0d1958b6bc7983aae29d9430914e4c09247d53fc8a1ecaa20b93662f4020cf7331a55701ee9fa57b97c9044d7048dbe7d1a9a350c96e0e22b1d808e8d24f17e233e0c864fddfdc7b68a8877c73cc191eca7eb9f1000000090922b263725ba81ccd8d7422243bbb9806961a47e96da934a3a5fc4603e5d5e4e6bc7d38fd31c84aa1747287d9f729869ab9c0f32bde16677534a83148f9c489920d3b29f6cf68324c2e5d864932dfa60fc97cbcd4d9657281712b022e29f5f89992cca034a6342d635eba4c0ad2df5af89904e4276ab1da6a39432ee1c0f9a8bf0ee4eacfa26a54d1a22a3d63732744a00000090c673de086083e4f2deaa6a3f34a917e374d1a9c2577ab6a85ff9bb583bbe7fa923f3efcf68348028587ee4b7be6df0a97c6af81b93af58038760a59f2d91bdd3126fdb44c80b208862924a53122a75d0d0a70b5433714b1eb1203cbf165a934a6568c68a22d358490deecff0558ccdb30e006a98cf07610a55194893c9649b30398a3301e8ba5d721279caa106549b6f000001bc000000904a4029e9184666e191b436b44bfa3a8857c4335075eb18bf413257765e02e3e16fcc66d3c2c32e339c0ae1b4f99a66232adfaf33417d71e3fb35cfa8272ab18257d412ae7c56b7c65e1dc1c4ffd6ea5bbc3f16dd46d111c38999b5d4ad141910dd482765f3cdf7d6fda72159ecf246f4ec5e09abf857d3627a4d90553dde7ea3f5663a02b08ce0069b78cb5e4f49c0d8000000902178902bc93e04b3f3a6658a3ee3cd0aef2a16aaed6f0249aef5a026de6f516849832b547d9a4e026ced2da2ede5ab517adadd045cc698446827bd8bca4491e2521ad944320d7198e23ae65432a90c51c7d0c0c6dd2bb78ec5a47c241ab318810f37025a301961be34aa75889f13fb67f39c272222d68fdbdf47e0ff5b3673b290951c674a463c68ca3105bcfde34ea600000090688c993c3b2a470cc31c4bee959a98d1af8c12d21e520b50773fd7723d9d9d6cddb095a99236d054ce3c11c6f557cfdc79bca98c54964cd68944b28f65831eb3ec6086b226077d5512c782e030b57e3c3276da66d8eb3af1b7d2d76a2a382a1b6e7f71e1c4130bd24b864dba04727ced8abfff56ad35533c1fc7eab7ef08949d08c5208ae3e042e763322cbbb0c65776000001bc00000090d50be3ce4e58c66a007f2716226fd00515d21be6161efc71317f535c0ad3f1cb0497aa0c55e3bec7a34675504334497b6958783f1090b88ee174e107759ae3706ef200b69e4e9681a67239f9512c4e86b101df5bf73f08d3fd08acef16bc9857b722a6472a6dff98baacace6f3478fa363e9500406db02772c4fd328f7bc82b0448cea431a089f6f567410d44992c3d100000090a2f72607f0f9161f24cc9c00ba8914937e24cc3d20d3b14c751076ed5481930c339928038c366f5fa1db32c055782c94b44d7e6a73c72b8370740c9a91972eb71165516107307e18f0d58c0dd51fb0585f05e6b64a7903f7e427ba93bb69a705fc464095757f14c5b86bab8352b84a0066210a190d0ff68fb88794730e6d63cd8ef03f632f8a6d28e7deb8a9df7dc83c00000090b5abd77435f66a78e2de4cdc07f5326e6ed20a594ebb0b3dbecbebcddb6ea6fd756b4393b024cff5cc9a178ae19f5088f6130f37117b37bd1bb5847f352862590f5a21dd0b1cd4060af8dbbb7300557e12bbd00fcbf953b4111477c5add912c77cccfe78306b3f29a5186bf4f005d948b3eef381081e2a8693299484d28445e4843b48c6b52582d8a2b5d84c749ac950000033a000000ce40000012800000090229388b3a431be6b9653aff04b9359a8608767dc2de0419efb0b91f247a4269f45a4b1e2a8bc2e6dda32f707eecd34cb3366b36803954fdbb8f279d852b37b33decc47345388d527433dba1449c00d03a490e6b5f7f0e9ad28301d099ac3492489f8287165f5c231389123b86db7baae55731f7d2ca674378556f89548ab1f9f037b5c982c7b7ebaa74ae94e2dc4732b000000903aee5e522b5f56e536df8ef54bd19f1c5bb1d820658fffd764da995e584a6efaf04d744beeb6127f19410af6fb65edd2261dbce88a18cf55d47a07c5c550ebd1d6425136927d40bc78a13c45da9e88993ef764aee2a53ea05dca3ed57368eda2c5a6a13b08687360271d05d835b94b78efe6b60f2839355849495a363ded3507648476e19e3cc1d7903191e5bd8598a60000012800000090505af5b4991216986ea5634f906556df448f90ee8d7c400a6e96e2d2014d9ca502059dbc82a4eb40e6422cc17d2c59399035a13cf18494af6e79de74dc243f69a18355d4ce14c1121ec65ec9722c4667e31ade9bb4ab5c41db9ebac1f0e215dc5236dd747e0ba8685ccaa12760865546d52a9e2245d699c8e88d89cd183e04c0ef8b4b6547141d247937a6da6ae338e50000009094439dac22b0f4e1a1cf2747ff890355cdb876d1133463d0f3016734159d099540628b8a81562afd699b1bcbc2885ef8feeed751536d9bd8cf046a0bdf0398984146839da849ec6bd897350bda300f04276a1b7d9abf799776a3c64294b6e4c9c768e19f0ef758e0fa97a706e6315cdc7a2370f89e301ccfb5664a9cc5becdf9bb0fd015936b984c9b4800e62b2e1f6f0000012800000090e0698c6a57315526b74f6cb3617711e0c054b0488cd2b3d413e18674be991c4ce9d6796564eaebb8977b5ae75ba9a47259348b64e8330754cc5543c3b867da1709fdda0a509cf7a216c6c8693547cb4454d17181ad1e67c51da2446c1b922c87836775ec86ed5aba1e4e14bac831021dde12ebd15223efab74e73b47d7d42a64b255dbe1929a91438ee5d82ec2c2b60d0000009072b35250299df92ec033153bd0799eae0234158b942abf7c23df05e6c60809a5e0a344d7de0b739c3c478c00c4c020c9cade6a7f3c10031c47ad3248bc56b47ac7ec58f071aabb500370c990804be96157e63e41ef9476a965467cf588f77f2bb420477512ff881df4bc89628bf8be5b095170733398ffb463ad6347e4ec5d3345e35be599a28d5e744efb625ba90ef400000128000000903c56b6d74c83e54735baca8111b0c3e5496c7cc3737c1cbea76e13761c0c086366de8290722cada540d19e8d45235e49469bfeab2b09f84607d6bc069409fc29d6ff6986e715caf2b411343e1b4e66c0aaec16ff15335f3030803585f8769fdaff07f9bb95d5b3a1f25a3d5bdc8b8420d6ab8d8c8c3146e82e330abf83ad8bb8e8153ed842cb780af11f4a7104a628e000000090d88f4ce50221ca7385fb78a7db3e639413b03b803b6bb177f100c95007507af701f9b4982607615066f02f598ed06b6f98be07c33e8283eae9483624822dcf36400e8193dfc720d12e58d57bd1eb0fdaaa014be61644f62ad2401ff2460f91b9a852ecd0c761ed2fbf6c00e721cea4f4387bc825f1bb92d29dd01e7aa31f4aba59a2188d3c2590b52cfdd921d7e8e57c0000012800000090aab78c0b3962855a0dce967579343c0137331b1cd622dc347ba3b6e3d66ac75c602484fb7a24e771f9e1b3f9edf768b86614c5e12f5aab14023a1f35e7693c048468d51e1ad2f71bbe06deb0a6f25b348372c25388d1cd7e44fb2a01194786bd2661abf38e323be07ac7e7c869348b6a0fe03fe712c3c2ba944353e7c161715aa4f5a6988a0531bdb757a5b96bdba738000000903dcdb5cead4dbc2d6f7f4dd0093b1c14f44876543bf016543b15a05938d6fb66f17f02a0e5467532f8441e0699e20acd7060a2b6ebbeb04f65af86b416b9a081a5cee331691a934379450b2bb4ceb4b1cab8f5fc861ac3d2acbb6485e2e689c91e09052ff61f9a819bfbea097cda0c6474f8a788a00cca8d4c863738bed83bc8e16e0e9759f9ac190d2b28987ea4231200000128000000903cdefcfd45b1ba2d83be0a46ab7dd242f7e5e895e7672f50add523a0eb6358f94c04302bf690ba420aa42a69e82a4f373f1887e7698ae51d11e258901baed21b6c0d6c3d16de591910540b9abad5c89a96bdeab09b27a752e94dcb2e71ed8571c963df9afa3d8d736b0621d941429139aae3da4fafa8def367250ad17cfaddf758914fd43d6901a92473fbb27726668900000090985b0016d8588b238dfd905cd864cae91d3ef4474833887e1a0119f5b8e67441d409af7bf106a2a9a10b887bf8edaa4d240f2de9cd0d7a180819412f076d2502431195001c2cb2b624c249519933e40cf7918e9d8856a8bd927f761bab2a4d2bb1a8292f4d63107f806f7fb971d4e3dbfacf5798444c335bd737a5e00c78f148c754f8d761c3ff396af00e6808fd01f0000001280000009001ab2f2f96fcaf3ee139547dfcb20712ace6ae915f8d86d2aeb96a8efc356a2b0c9e40fec9b001f22ebc49c53056f781aa0145115822ed88c143f12a353d5f0470fbba36e85ef4491c85c310feaa6a20093eaa78eb1e49c815cca3fbcfa32a942f90dd82ff94fdb74d66a089fe8529fbbb6046b30533d144f6a537fe8fd734ce32bbcab3bc2019aef32f77692397b2e400000090ce9fbb834c6c927a11fac76cdfc44a00edcae62a5d8088092a2e7545a3b4a7d6d58566a30760b7b3f4e58fbfe571c4d5c72da8be7b7999a7c29c70a02b868ef5558b2b70b1cba583d27898bbd2d66c2923944c85d4fc77d36a5241780df91173f2bf13e9c814f68c1fe6b5d92d46f7c1b2bd1e69ed00017262f1d8b1fe795b498b920a9be2086eb2f00b8f652964039d00000128000000908860e1a69a9e2cbf88e05b93915ebc98e8b609f41781ac97fafe6c3e18e8c276e6faaa4b252c784a9b8c09e1caef1265e0f2c467d6272fdc889e539ef0aede3684937782e4825d9aecc975e09770420f04ba1333c84fee4dcc917d6ee0e567aa421f205839425a57597de409eecaa93d3a4fb262f9a99c14bfec03e1437b1108323ee30fae221f925397481c6dadcad200000090966fed22f972cdb59d1aa2e6c28c7643e2d1a2dcfd4636345c21e9d2c702dd6e9c97eea8fa2b4a5f2ce409f9706f1c4f7061ba0597dc1772c5252d3f5c0996e17fb44db31970424fa04c39e4f9dfe1df3a19ef6536cf3678704566cead85577908d2920d4b8bdc43c8154712f0e994deabb13034ec5db65ed7f1a0099cce611f0005bd6b2d2fb8168fb09b296d6d3de600000128000000901e24bf1b818df3b4f0c786e0f65346a46cbc6e32a4b3dd7354a3e49e45474fa4e90e150e00d9caf47ff6cf2ef7e5af39576ad7b7afd93a1271dd3e6cd08bafc90cfe66772ae41fcd3f6f184fc55fcb61ee464c44442f22a5d6ae4d8423fbb03d2ecc706b6043e1762d51df7c4f72e4bae329325f1b813714178ac415fac87060cea5fc7976804ea4d580b5a6a9f2bc320000009069200cddb6678b395690d22a92613469428b1d487c79be4450ceb95273f6b409c3dcfef361f1a0fa88209a31eb26b67cd96374df6bbe2b32dfcd867d00d8d5f4030e3091d200e4d95a1c0d511f8f37ba31378e1bae21b3125818a75f52fc89fadf49b10125af4c66631914efe6b68bb40d1d6701e52cf14587be5e697aba1f2427d67356e5a9c10f3c6454ed23af43f9000001280000009075bdd58d142542120664c3a621de692476f985546d6e644cc10fff950d99e39c3dc88d84dee487f947bbb0f3917c292f4276e9f87fa5724f0733daa0922f338c1cf4ad40224cf3ffa9e0ef29349903fceadf23ab67bdc42a6d97b73bb85352fb539252d59b6e7e86238327d630e0a4f6ec9d8931377580c4f6ba30f5187e4d59f60f1acd6fc96ecbbf94f13a92830790000000905ed953ddd64ef5fbfe5edb5fb182de41aa7a1fd5f9e85b6d5ea74cd8a7bdafaae31a434198d8841c3718a07ce17edceaa7e3fbfef7c0cc8aa893fc8115857754053d8c77e1b16cf1feb8e3d0860585641d188ea2c414831f3624ab59d835851f1bd033090662fcf445ba08f4876bf71590e5bc3c32ceb745088cb0f2ef46272da82ee42ff496bffc2ac37a20d2bacf4e00000128000000905a341b6ce0ede8f5f8f6e8d32e8022c11d0c68fcb7aced295e2ad1a83a9596187a46d257d062ab1c70721d5aa577dfa429081d6a355708cf37b903ca19764a3c0dab929758e5193aa8d12150c059339aec63bebd51c3092d43145cfb9d3c451dc39d6381650d37398521efabadbe3c1f182848defb521b96f8010d5ebb0d567cc1a35b1f92239a47976e2b32989ca7d30000009073ccdd690f4eafe2eead77e9e089ba7511cae490f3538e6c84934e714a4e9fe512908ad79cc5ba5e0fb79dbd9e583c32a9bf87ee48c96635a9a75be8c35194ea95499f3249d0a1bd568e081c6784f79a20467f0d81545aa21a7e38ecf6ef9d94dd9f5df93bf563720b644bda89f3c1e229db4cbe6eb0dfca5adcc805e1ab68da3cb812817251b02acb103aed40541a2b00000ce4000001280000009026bce16f9034980ede54249ed45fd623470304ac7d5c290037b743d785762c0178dde6a5b11b70f67ea2aaa4bdcad784ad51517c0aad8d0741ee9896cdfddf5efa8cb971bf13704a62ea56426c5b5b28dba5b9d7431f3b703d16ca928b14717ad847beff58bd00703d469b04ae8d5673c764459d7c70c487e2b8dc56921f3772fe890e19d46937246b6ecb53f4f50c5500000090f04587d4fab3a75048c04fa486941e4e874b19ad77ad780f6074d2384fefac7ce05c28ae2ad4bf50e0025bade67aae8770584ba9992b15db5533cf3b89fb64677adb54c862e350062e733f35edde61fc884a5293d24e20c4310964eafb3c5960d7d1def9a6f609e98b1022a692f0fb786d81df0d158fff49bd11a990c3122e2fb83684fe6b5106cfa2733c6fab9fb88b000001280000009035a7eeca40c8e2bba2ed2b757b8d6490f44e13e161354541e0075ce961822a4eb27b051457f9e88be8684213c8fcaa00ad2052fe2886e701cc24d8d6f7a4aa4b3597c7d85613edf04dfcada7d7c39da86b077d97620635b6dbd29824e0848357be256b504ff5652165c4822d4ad820ee2c79467b423f5d31adb633612b5e5b15777d6ccae55ec70d99a6c10c030cfe2a00000090aebaa1ebca372bd33e7852db4163446d886090bcd05d6e5355d11047e601463f4507859694c8e30d11c9d74acb02a5c7bd5eed8dac2d9f0e5d0281007233a02ea4c0ed665441aa57c0c7082b942d65b53e605ec7d3d89af83196c02996622bd3ebaa5d54b7d4da94727478e2987ccb4bfb81a153a99603930fd609be0e6be983e1fd59f539a91df3e9f1763142f9a6ce0000012800000090524c471a92b6d7165edf28ce0e54d66a6f61e270e338d4cf262c8cd180db7be85cec5365b6008feb5be52c99461328e1ebdf6893df6f9808a74e2cd5fc6c60919000a617cb9a4bcf84e01cd6404e7d5dac142e826e737dc8758b90c4dccaedab94f1cbfd7cf053d8fcae94bd309f201831e0edfc6ae8e30457e0c0371031220947c78fea955d4680fe39ae790b9e963e00000090d3bd94123ca96bff2dee2aa370efcd404e118f1180d79530dedaba3dc3154614e9915a8b0901b005c288f061f03c0c60566d357ef772387d8e61e8f4c2b8654c62c7cd23c3ac9c033d98892b1ea04ba5b4a9aca46eb7a128045a09269f9bd4fee8402b8be68dab85fd929afba6f29a7fe738be2ce0e94820e0f9c6518bee1b44679c9780f200b5c6261802b4a0be2b1c0000012800000090319c0d94159436bb81fcc81be0aaf9e3118a5955348108a8fca1f4a77806ec0462f1899c78170ff4176f5e97d5a8d2721e7bc03a8eb6dda0351850ba330a2b54cb7128286d9ad0d7b58c37d112b2536db4d6a86d66a3c8559d4cdc19caac191228d72367ed35f94d5d2dd7043ab074a7ae22ffb167347637c3ba2ad3a0c419d1181e67b061a1de2ac917f012999b1d0300000090065f2755859b1133d853b59bcc80bb73af1189664b3195d624861c07c9eb878cfa8687cfb82c4f851b0528473e27bb9af77059ae1c4e23a5f2372ff75857075a753a9c75875ae2129823424e66d5d78f84afd052d61b0a1b31a3dfe6cb1a806aa19a50d5b9ec157d2facb4f563abcb83cc3b6aac3dcc528c329b4919a355907b36cf2ee524d926ba87092509e3d808f70000012800000090fa2875f8be9eb0776642aa5d2bf2bc615e324ee515e2d4bdf81c09de95cb6ab3136288cc91a3e145bd70fc686df1754f7162baf30e724733a98258f91792da9bbc4264cca247c12d94e1fc2f2e45f9f931060c14c2462f6b0cfaa1a6286c03d3e30d81cf3c03f68af6d107fd42cca7438ece9749058fbc2328dd9bbe02f4f8d6d2309e6c3b0001af3cc932dc8bcc8ed200000090f50d7d32ab91cd8c3aff752e922844a0bfd5edef2f6bf8784360fd5b486e44288b07615b9d580f34a302222225ceb2f04d41339ec684e624163d4917bc8e42193fe14b0c1c88d68f88f9067ef677bcb76c752f326f2e536bf531c021e543fde42a5aa9e1c499bee05f6d78647b9d9d5ebbeafb53b8604767811f8e2ba029cb438e555810adec3f85ba2521675c2750440000012800000090d3e5f1f0b7463a85fb887c73b59cded50445fe4e890daeedf6a576380d2b3fa11c7518fb25af49490abbd6712cceb4305f3d43d61349be0c9b4c393a8a6bbf30f17dbc06c2103572b1b5cf22f86daf99eb76470d469fae219d26c1dbd533f47cbf1a3fdedbc55ae23dc2a789e51a8ab28eb2a0f292ed0c9f7253913199ccb2a910bb012a2ccf4630fefa169f661ad3650000009010599e417943025a7639267a1e18127c1cd439d05a1039a059f8ab49f9dcef63b890dabfa6096ec7fa0ddad844b29842ed3ec56bd6190c811da59fa70a07f9f5571dfa7217aa63717e7e6409ed81e37701851638904cc026911824d6b25c702fddc156b58f5f48ef411176330f9fae31236c67f30a1650158369ffef352e4ce3fc6a4d8cb5fa6ed9c5ad9ad3d119701a0000012800000090a389db0fd184113a4e4b46aa17388cbde640bfce3325cf61bd8ad7cdad47770eaa77abe2e690f52076a252505704b309cb8851cb69b24c95a87489136e6f73c0bfd49b94a4ef197d01d754d1ede85e4663e923dadbb4a4b806746f2d1ed9d992e06e2dd80f320f827db44059e1716ea92a1fb7c77e0d624d5adc773982b6c8d6687b699b3bdcd74edd00d78a69658c7c0000009028f5c952c4c3cae898a4269ac8ee0a0d4168e771a6f8d58561fed45c016c95c495c721923916f6d187b2523f7df94e7db8342aaa50f7a8c76823499ece6d3a53b1e095c4310c0225f60b20a91b86f1d2fa94c0ec508cc26b7b88b01a4b3b17e06698d586af6478253b877068f6a846f18fb0179240bb56e4e54770b3a4bc215c2be2b50673dbfe7bad40c07f63bbeff30000012800000090f2c814af6271db7c8e9d4e66cde9621bf22aef0134da41f47c35f71e471322fd66ebb39b40b974ca386bee6418469d5a1f3614758edb5ce1fa9636e03092329d8974e695dbf86e8da247feb4968a7b061c729f685b9da1d8b8635e04567bbd2b33eba0c833ee0f0bed056365873336e905c137ea0f17978259c2ea780c8c25ba328af85d9d98a0e3d938ae4107e42332000000906c52a4d740a9a815af7a7e95ba09c78226736edb78e8802d418b12022485c3b7ce79547cc3dee2c8001c0955d2601cf7aea7fcef6a09f1e36410a28a233be825bec542c9dbb53b6b2d203676d2c62b06d180b8fc42125ab058b9155a27244b61552af9f69f214acff4373f901e83c64a49116c2dc7b9b312b4fc93de2bd45a90864fbdf25a4ff286563a0ddcb50ecc770000012800000090bc8e92635d068e8f45cba5df8453691e130a9fafddea39c56f766fc987ee093e76ab21755ac24471f4bb049e3e71af69d9b12bacd520d6541f74909f903d3ad05830a6453d20c79fbcfde7ea423693eae7176bf537bbabdf309fb4e4178f40ac2eb048540dd302daf6deeb69aad9617361c56304a9037a66d39a94c487235c862b95d933caafaf0a964204eb00b2fcfe00000090f1d1f253bb1383ea73d63955d73b688b3daf0a019a1747ece001a07708988e629e5cd0d557e8f2f674db96e904a69762d1bc2eb02083b535e7767d14ad60fdebc1ef938d8422cbcbf9042ffc27a76523f81cf17bbb658d9cb951fe0bf9335520c00aed26cc37b7ac1eebd297ec02e90deb147a3aa5f2933973c37738cdaa040db84200c040225e7a4d412cee477998210000012800000090b864adaecd36d70266605faf87d3eec7205abd549042d44f60ffff66ba0fefe5f2426826b44c2779e144f28a162d06d24efe8cf29c337333c8ee42bdf40e0f5f836da60d7ae07495f821360f22afa06e85fe1ff0710fe0f12f2e1a053cccd9a89eb8d69b12d0604daf20a79e2b310d069746d271cfd788b448aa9c907b0be49faa548bfa0eda227aee965c8f4b0cfc1700000090cfc24b1d4dc86a7e0b31a4b03db181ed811ec14b156df7da4bf44ec6a19d7154ff5b7543d4a117176f0cb5a509706a19736b86ce8027dd3ba62ed0e1bf93bf2686201a6aa33bfeac26fe49c2a91dedd85a8d0323668373868b40e65e662edd3b079fda7b450524e89ff5987d8a5ede5117c005777c90a426862f76bfabd4ee3f254525478d37403866cc1b536b24519f00000128000000907340b3e06f362d0e25af7a0054eaceb640eadc92c26ce2adbd225e40075c48adeabba9a0286360795e5aaf7ea15560d1e9ccb9307b854729ef54170a63bc4584cc33744bac67b66475067b808c7b3612837efb49e00909b129456195dcce20256d5aa533a5ccf6c81644deb9226458b01fa3882da3ccb1cde0778a76e0ef1de5a397b1a74d1015da520af795974fb68300000090d2f4bebe950156f5a477fecae6f502f2e12d0a6df4029e8b4bae461d325db1d3b5812a5bb055406757f41621c76865b80d6be58a8bc5d9916c0627eee5928cbd9531e7977707dccc5c37a1a8a91f61395a77fa5fa5b7a70b8e677d311963474577abb368e9e3e571a3161f82c4603086ee0acf8c450ac2390b5e11e41c161ed9490c0605cef9916dd7ab4dfc8639aaba00000ce40000012800000090e549acf82a2d39497468fb5b925614c77bfaad29c7c2097eb12481b38da2cf526d83815bf1098dd63315c17a3cadc92ba6f513002cdf6a388f1de593f4b1075fcfb8ba1775f0b9796dcca84eac6eb940a7c45a9fa6c123681ca3d9e1879f61b842aea625a7de75502dc5af86d6b159b7cab7fdefa98b2db1345d07cf7da54dc5b1735ea31d2e75bfce4767273bce88cc000000905b57379b21c844b13cf8f3a037091cb3d0badbf3bde4c962d6b9c3c859e71295b1854091dd1c6668566ad727f9020ade69e19d34adcda46d2c93260a54929ee78d0f54cd90deeb0fc3d5860b7cf373b1012cd224e4bf27172a5741012cc83dd2ce8afbb70c32e3728fe900fe16284ae8b91f09055fc133f32923b06fa3f594635d85f964e8592be2175673eba3b07833000001280000009068833df188633bdd6038b3056a6f3c496a25391b1931b2f7cad08ee450a9a2a319f9781f9219511d9e739d99a6339a386822f561ffffd4d03d7fded85c2aa60202b8b67c104a85455a269fa8ffe4aa104e510b61731cea00eef07a943ea86867635706b6dbc509ee7e710247f254ce37315c9430368fe5e21cb5868b0f8c15d0494baa5aeb5c6b9a3b2985b3791b9a6e000000907676fe247936a75a03de9df952f34f0e82cdb40a1b057f932f6b913d30f708119d8857a1048511ea14e1710db2b90dabdc1351d9521f5f3064367a1d2db14fdb33bdd44e155cc39f61795f024ef460b307624c7a5e004dbea07c89cd271f921a21d225bc1cd87db49e398e1e8aedd923c9032f07b7e29cb1ceb462a59a6b2f9aae7cfee6064ac65578fe46c69cac75ca0000012800000090811ed27dc7ad47725b36f2c99ddb7e4cb66d6ddbaabe12556ab48b82678be51ba35a2c8401214e1c52e839a8a6903345b56c482977ab438bb64ecaeb5b6dc871e3a2876cf72333935d688ba754f397f3b96c795a3b292eee1f0def244ff2d3055cff950f727aef6d10ee069202325a8e154d526e08bd9dca4115276a99642612af60bb882d18903d261879dcb83c388a00000090e215b3deaf1f80a0450ca99498b82978e61011cfecd2b62ac1302b54f5abf8353658654997d2d66aef07ee1cb22bc33ac492274e548657b9d72c33e15883ce164fb56df05ab50d4cabc5ec4b3e328e98d595e36b1b778577cd9f07aa530d8049b4e39be4470cb7ae5afda18939496c85969fc1d3859992dbaf71a57ab515f4ce475bfbb014d1a3b005d698240f3569680000012800000090393392d7b7dc83078bd03116d5331cac8598a7d3dbfc13828d4a755de00f03b2ca88481407fbe04314601bb9001255aa51f43898cd2eaf0a1c3e11987b40fb4ce9d81f838de5c7d1ecbb79addcf3e27662abe805244c5b3d6fe5bd6e542eb329f48584e30d09d103679f52268d63abcaa6d02a0e9596b1c5f4d54c4fe11d8afb084494242cef2761e6be2b26c69cf28200000090082406fba8c9ae1b7c48b88bfcd627113f0d3f8c845665439fff90506e6d10257cdd5039c84c2b726a793c46fd7e30553bb33e2b4508d626e3d1deb2742683b26bd96abc118606f6fef1e0254203743f7ba5e122cc28a61e7c25550ee83cf7f363fd0ff7ba950447f08f868332b7369af8dea778aa4f2bdaf19a6ea422f16b9b4336b90d597c150712c3b083fb5dcb860000012800000090566d5d24fbeae58464b0615822f5dc1a5a03e6387afc0f89143738c3a7a82202b4d56c2bfc68175482f4cc3fdee16e023931a4dd760b719f0e31325a7198dacd88e32019efcadbb2a52b9701d23476fcfaff2b0f621845c3e246118f5c45e2a6790536c3984ae9a5984b938c3297920d7119a80e9e65dedb85f624888ee140d33fc9acd649e269a3ddb1f6bd31663220000000905b94546562731f1304d142db43c5462d32c17a7571b9a3fe2fd8b6c594b07a1bbbe4307e8b5c8c79213dde97f4a0ebaed453c4e798137e16b7d97c5882bf505ab2febbfd0a9bc802ea653874f11d6428cbee3a13a18eb54aa068cf2493512935163c59abc5d41900687c0222c084e85b476cbe9e2c1b367589a987f8c85c6b31257a22cec6af243663028f6d04b83955000001280000009047f97e0eac0aec21000ef407ba1cee999045431c0b8dd55027275e04502ab79ce4d0b6e5e86e8267a4e0234e029b335fe0a5a425638b5c07cdf37819afcd4b1f89246b02dbcd453bd7c2b0b25334f835468e6898a5c6066e62b8d869892dd294e05c83441841902532b4b5614c537bcfe557e329721078343e340bccf1abd74caf709ba0764352531308a7f5abbdf36000000090538969d6d762b76699c38f562873016b04d775021b4e9d2968430a2b3037c9cab3ae7814fd6dc7540e1a776bc4b75ebcab68a5fbc3eef095bbd0897517073639167327c8ce14e0e383c48475123e958c0c547ec4a2304df37901c05672f909770f02994c6f952eb5f21be1782e8f9b58df9967dbbf40306aa622ae9b4f8ef918105bfa6490e6311df30d9caed97e65180000012800000090ecf13baa64eae5d4ae2a2171abf58b0316e596ffb4e45d8b04fc6b3ef7baa3b6f144c69d47e57f4ce71725c1d23d904eab72f1e9558b38a2ec2e03490753746866158f8b5a1c1676b6c104795c2c72b46c02a0a24b09b0a167cf793e6d3a8fc4966487e6b9badec4505406c897064d16397c8f9c19e3be5bf2b6d836598a05aac481b44c2279db99967c7af13f9b754300000090ec3107180cd218bea3427fb271d8ee8dad175dcff2c80f1d9546beb9e3888f3da8632ce4672f5a4d826d219dee2704ac726a25cf699bc587432d1e76c5370cc090646ae5eccbdd2fd291b96c28f012c846308c58341f691de11365856500c6ac11ff5a20b28b39590fa03cb51730afcbb5651820568c5287dc99816279536f9016d8d770bfea03cc7dab39d4727b62910000012800000090b10f42fe05e1e1db07bac42949c8ba753f1c1185e27e061e5c73b8cf47eed8f356f38a8253891f322bca6bdd7d83933291953bfe0f225edeca42a4903bbdc73484d2b13347bb5b6ed7f48bb2215cb7b7cf968b29972e7ee078af4c634100613d21fd151fbe189aa19b395ee32bd671fd7bc3d1f9b8add290760f8cae20afbf91262267fb012b19527b11f94a35b5d8e20000009027517a0b48a659823f506f85de46054b9e586a0b9ef8de12a8d5f126d849333211c3e95f572cd162217fb7512eac7a87e0cb71a81c004a8d33d97e85832c1b7d33d615aa220cddc2ae5389bb239b8e29652b02e322d20c5e193bb3cad2efca03afb2d04c44815e56e2ac9caa2b68d32dd291ce3da9c7debb6646c0de58f4e321df762a3cc55cfc30425221a2f2cb26d10000012800000090471e0b99b2d33a1c6c46137b51404859c121b605ec6ba2d051cb05000ea2af1038fd2f55e333b12143126d8430e576a3102a4cd9b7d8b28bda0fd68c80f9f4907d273e8db07d35367eff9fc93d715a1e1e20c80b76593addecdab503c944603e1ace2d612f482eec9d23e9655552ad9b5d6805379b4ccc26195ad730f4a192cb4f85589fb9f4f0556480a78593348a5000000090b56978aa1d94ff2e2593864144defcc9491429a862274141a65225b58597d15b8eb13ef060cd75c33246df88241964c87eb08f2639f24048a0dd94bbca3b0fb4a6fb82916f2b6fbe1d05504f02fd6b4a67b58c58085864dbb6a26f7359a1e256e7fda12e9f4714dbab35fa1ff05081380bd351516c68b542f7068f2ecad24139b59055599b1cecae902973dd934007a400000128000000901ff8c6da2a57ce3580ed242c3571d9ec27f35ef436367a80bb500e65a97d49e48840fd757984eeee7ed4d7b675e2fa761378d270ceb582a96f4abbb7b90062747ab25a1e6fe505e7c3f2187f38c8996d773676380161053f7eb3f8c97387aa9c6aaaa30b97a546478668ac11ac2447f8845f1139820d679387fda7ae38627b95d2e1bc4a1ec6c50c02b6001ad67dd621000000901a0f38d26a55c352cacce5719aef19a35195aaa7a0660322ecef21d17cdcd4beb2e9fdf909522e68ead4974dbbc61ec727b7c192d649ba9402ef11bab69a4820c4ac8466678684a4561b79c607787a99d0ad4c9bcea79c0f6141417e2b2c08927e61c1412c6116fd383ce2b39f5284887477b1af8a5d4287b6e93cd9f6cc2b20bee631b52586254fca5b4eceadf0fc3b0000012800000090d454eb3e205fb713c0e1f57ac87f7486e283bf5373ab8dd7d6814b51e647c906668e53fdecf34d62c2577741f0326aa2770dbe8ddfa38feb113f296e8154da0bfb8744ffdc53fd9952fc2886e3c9ff4d9bec7fc40bf3b419f7cd8f9fdd94e5a80d858c55d3d8d6a78c4f38aafa2e57d344f3fa33066c02fcef95584f53fd18d77746707d7baf40f3d132e0762ff530da00000090447038e5cb74279855f930e8f7b7766db0fb5d9659e29f100a5edee666e93509b6304c0e244f64f622b85fe2edd61b7fea4129ee9877221583820e8624e32d92ab91f7f912589879ebe09ec84d6188f1afd24505c8d53576e2f4ceb6ec542df20ff8b21341611074c545fbb75dd8e4b9ace680bf1c22d34acd865dbe1ce75aa488b8c373d2601600ffdedd741f163ed500000ce4000001280000009045436ce2e6da0bbfec32c2a11b7de39d7dd557b2c44a87a827375716d3a0bfbf23e4e3027a036a17fe39610b53226ca60bc1a401c108e7a175a79639423d25e87c5bee44843faf52e06dfe9c5889e44696d84e60b00c808fc658cc1ba3a0380981f00a9b7e08844bbd7a30005ba8721b1933fb86fd11c4d51834fffcc1ab349f99bcda485368666b15f1e3668cc181f200000090bd229cf6d6f7d21631b59a23f3dcab0a9e2fd1bc8559a231695b6a5c26615528cf204d00046fb6fb2e67656576cbb6d72d194763c42e03a99b90950193d0d71c906007f18e3e1e3a7dc32ff538e16de9f4fb1ff8469289acb071318c67b9e99de9220af87f0936e67dc7dd73f06476d2c387fffde703d4375bc237c735801c71af114a06051621aead6dd13729d131990000012800000090b1095ccf7c01d057426cbf1f0b64a04461f96293107906aa032cecce60a80895415eaac9d968cf92b9bc23be2e4c28ad899b286877d856e516c45346416a54c7b851d3ee2310822c48ae58b6aa98d77214590692e544beffb4cd7db0b178bd4c73352d26295117d7fb3035e24c5a46375b0351068f8d13314c72c4791ae3ee6327bf1a69223068ebfbf3ee1162b05b3d00000090f9c41e0deba7b51b30183beee0fa118da199df57e7b64f7edabe514b9b054f84a8ac4a874b180c1173b428aede80f9065673f90cb90b50a75d45190411bca2802ec52d39a94964dc713f22e7a84e2b3951dc0147667b61153d4a7f05acc134188a20f930dbecaa1a6bf25d32efa1522651b47f92bd69e4c23a636a55111254efd05580201b50b964a1f32dc9025107a200000128000000903e89093271e6a69b711d07022e4e5e9c150a25620bd439d17551add63a0d8d884e45a3c44128ead9c2d3d8d2c3d4f92a39c8f93cb7578f1163966a8fbfc0e9c525c5d7f722a08d5c7352cb9a75b416603403f181652a7b06f8ca9600e23b4a965cdbd77ea9839d8e7e4d497c383e5e37682638f763ab3cead19ed526720b89d95366d5ff51b3f9c5ae658633f3e2c993000000906a4e1ed85756cd8c10680d71a1836879fac29cebf1d66a65cafca1ddb5b9ccf0c7eff289bdcce8b3dd23a1f904bb453359354d48579cc3fb3fbbf992a3f736154b6c8e1cb2e51c5e2c172cff4d5008dad02365cea9da1d63e00c042a9ba57e413c4a06d155fe9b9f86e262e97463597a82d7ef555baa2341426e0b7158829d50f9e4ecbe06444b6eeec16f96faf4550500000128000000903dc7fdc3162430d001f7977c0763f2ff678dcdf07c2f6a2485deeaf993ec94a2198d9ef36d7cd7f35081345641df105b6bd11a3491769f78c0d903ff1431a9e5dbe4018ab44cf0b9821ecb5ed32eb34ca80dffa898289893d917d9b1fefe265d6a7f8399c9daa180b1febcbc1c558d4746e5704d3405512a4fa1379925e58f33fa44731f43927a950a6c8babbf79a14700000090c596b4a820d0e7a8836b13d5e7f50bc9755865b70d1346e590ca091025a25e0add8c50137f8192b347f75bccce887e3f831d57d21252cab4b69dad4f17e1e0d280d147c23ab8bc57080c4812514ef05f63506743e267f89012fceb91c0688d6f5c434d4c5ff2ae87387e1a186b536e5bf6e98ed3291e5342ce9fa44c90cc5ba90387a4717d6dcb6e059145c1ce8764fa0000012800000090283d76ab2e5af53de1d18916e20f8b86bc6a20176941530f8619cc9cbc9fecc6a93b724f7fcee354a4fd6f195841ab11a75c89d33aeece8eec50e5fd4e546caabd8c04c6b1961a9fc37bc167fc31f3ebc1eb5d6dd77a1d04c733862b604dbfae14f6f9e69fb510ebf18bb64db753b0555b44b49f6fb37e41833c2cc1943c57394e0bd0dc3ce650c720e383a2de6f08fc000000900be60fd6de9a29388caf4f72b62772b80a503fd1449f8de61c72274e30ec13804f576b64345d7aedeff7af7d4b7ca95f03409036f473f35d5007585a2ae57ab94e234c195c07e0b5396f64211659a785e9a3ae4a697f5dca6a0a3ee2800208a107b12f40e5ec28cef8cd611add402aa2b64bf8938bcd1c2e4faebb54fa98cfae7b56b4a7797d5773a3b13808ebf2d3640000012800000090e1a2d111767837b0a5bd9ab4aa040837f01dc81fd1f3b4c94c577bc4334057561d356f081587fe288df0698fd3ca2d1be1fd4533a3d985a514d3b47a8f0e25c6e71b77a0f2a845a9292d016e16dea8c48597750ed24ec27955b749b8c647b1dad52191d4619762d90b395cd161201b5801587c46c623c9e9c85461a0998f13f32f3dabac687c6b37f0452793ec12029300000090339f31d769c14a357e7c98d6f5844e7c8a97ff122b25e0138d144d983c8219f971a67c20aebe049899c7ec09a3c7673ba857a507af1f5df87b53ce538385894b27c2e51f69ca75cb36656b09eaeccbffbea93c8af1dae1a5e8ab4c7c576c1f8521f27aae4d35c5bfb2868134d8dec61c60ebdb108b6f710c7e5ad7cf07026b0ab30df451dafb7d129075b4e2e29e87da0000012800000090446064bffca204655f9bcd32448d5281fc2ff2719cd87b3af607980fca826d2475cbb26176a5581312a871e1f3437f8b56e3e3424c050b0431daa7232ff0628d3c079dd7968229f7f4052650e2535473e346836f424308e4617e69233e04cdaeeb36f1952d6b42c278ae462f8c494d767a736ea0ca5eb4c7213413aa20a50b425b53e56c525fb4e4c9a4446ac4eddbb4000000908c5392f39dde50a945b2a726a59b76e677db424b16a2c17a7ae63c5b8a54db1a3ed78c150a97792402f186fbb8a6e093293ee00ff8b56cba7e772d89c79b87d48dcd17d436d11128b1f84ba989025fe5e25f731ab1fd99185783c13e2b180a344419a78577c541e4fda696c8463bff0b69cc80a23b3278f1149614cac7a019f43e484791680e168ea97e21cd5ec8bcc20000012800000090345b551d49681f065d63636d7c6e71cc73992b3b301f9591c577572b36b22e0fef0ac09ba7d7cfdd0f49a13739ef4314eb78de142191a75976123da2a49640249ccbc2af096e3900b4ca9bd4f09f52600c95f374ba110d6af27c2e1e4bc776af4577cf8cb55af20f521be2a0b9840a9e24c76aecbc0452ba6e07ff5762fd337ad72f9bebd508596940230d3a3648f54900000090ed47ff927b3a0d86080173815ca57523eb6dd5e261e587c375bd495e8bb376c15c14fe2f7086941bcee511a2845b43dce73c03193cb7f4069a18a228f310c87c2ea4c7023a863ebf36b3dd8f205b4cdf8bccfb99add2b6bb9b47057840ae0aad4147bc702c940e977f5be5fc96dbed0f578bd3c8fb05e73a83eca17aab10ff63f7781d4f42164a7ade252a819c7d8c0e0000012800000090a2acbcc47d3d3b5de0fd05c6e9a434a45f8df67d84caf7cc08438cc747261ad6f7d5182e8b4ff771f889ecff987fc81ab3fce9803f1f66ee6042e70026eca90cd8ee628a14cb2819b80062b9583aadcfb87a4b07b4758b162c4836d7e3c65d1406e4023e50cc17103ad1f23a9913eeb466c9626c3753860cbc973345674a43a3a9c3752420a8d4d0968d1dc8c051e0d000000090bec08567dd021f5e9de396dea483bef26c0fce90d2a9810cc0fc5cf979273566c8bcc1a5b7348f8c134c6836db56f06e1ec9bf2f22772212bbd346121021a3e06630212745414ca8ed09a68bcecae7ae5d657a5d0aeeb0787c601fd7ac9164c8dc78b475ab03c24fcd0d069e26386e24a339e387c4aa4aaee8a982836b9fc4f7da4043a4d98acc25a6d734034ffbad800000012800000090529e399a8cd2758e15cd6288caae8a4caf0825dc10342864bb3420a5599c42935ef54ca3c0755585de1d591be4d0a077bef0d0c7bf6a960ce038af375b4a2353866fcffc2e2ed3c9c126a996472b64259ba5bb465be59a2d08d91696dbc24909500d5840aae4a2af179bc4eb0a409b4b8472380e2f327252ed83255cfd2cfdbf6ffd43caea70a475fc62ef5f0f88aa5e00000090d57807dc2c257bb03cc5721539ee708345108fcf0cd13c91142f134800d242e8435773da485f21220c059830b664ed61fb5fbfd7a4815b7e33ab387b4c5a6b2cd5ab5bb7e22608bfdd5b53f95d58276dd44019bf08893e0b527c19c3a537cdf76bf68fee369d43c622e64a8567ef2cb84edd9ca30480d88489f1e31117c08c45e575ed09a075964611b419728712620c00000128000000905c9800416b095f77831bc877a355db545151d4ff58a8d9347dc9a982366aa6da5f9043e7b3320854635dd53d55cd2f283188eb27020f98ab4a2735b23d1f91df763e3272f4fbeb459ea74d93d007ca1dd1162e57b48161b27a2f02b35a7ef5998d4bc0e60b7acb4d31f781a1544d992688d47fa439a6e7e4ce9a8224e4e1c36ef85dd306d8fbdfe2246ef96c98ebd7cf000000904e9a6442af0ec3b24f0e4e472afb25a70dfa8159e23a861e53cd3756e54a9dc02e5a0e2112c1e00c8881ea179393700d41ce4009e93b8c5abcf4244cb2705507bac7d6bf6fbcc6c7e0023b0518765b6bebe0f278ba18c4f1e62102b4affefd24d94713ce05cc156a4a9a0908fe9632892c639af5c86c471a02aa3dae340f061a23b94ca42551f6b15919096dd2d80e87"; + hex"0000000000000000000000000000000000000000000000000000000000007a6900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000020efbe2c7b675f26ab71689279908bbab33a6963e7e0dcb80e4c46583d094113000000002adc67712c2f7afc4e827551236adb46a693d572fbb29f3993dbedbef8d2d87d0000002027378c30a97c642a3b78bd34c54beac15a4dadcd7a3378e66d2384c485fda541000000002b72136df9bc7dc9cbfe6b84ec743e8e1d73dd93aecfa79f18afb86be977d3eb0000000027378c30a97c642a3b78bd34c54beac15a4dadcd7a3378e66d2384c485fda541000000000103babeb39cf7cb3fd320ec225367ac5e834aaec3e7b48bb3239d0da39fa4a200000001235f4e41a2440aa28f9ac14e7eed447ddd2e539179cee4c0941e6e408d2443e50000004026f32989eb2870f2ed00774f54a82e8266fc2a2b3392b64e8199aacac71aabea000000600b6abbab461cfb072b267bfc1ecf8dc3c943736341baf11c5c829e345c49b5090000000429c0d0effa4242b8d2e372cfcdfa8bb57160715fa3640a3404a9bba93725a107000000042fbbd267a1c9b23b3ac1609b2c2a7961a0338c56d62607d5f8d6b6a29e7fe4cb000000102d81b3e2140328e322ad47f083bfb89d61aa26a26795d34e7442006f939663b300000002000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000012100000000000000000000000000000000000000000000000000000000000001220000000000000000000000000000000000000000000000000000000000000123000000000000000000000000000000000000000000000000000000000000012400000000000000000000000000000000000000000000000000000000000001250000000000000000000000000000000000000000000000000000000000000126000000000000000000000000000000000000000000000000000000000000012700000000000000000000000000000000000000000000000000000000000001280000000000000000000000000000000000000000000000000000000000000129000000000000000000000000000000000000000000000000000000000000012a000000000000000000000000000000000000000000000000000000000000012b000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000000000000000000000000000000000000000012d000000000000000000000000000000000000000000000000000000000000012e000000000000000000000000000000000000000000000000000000000000012f0000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000014100000000000000000000000000000000000000000000000000000000000001420000000000000000000000000000000000000000000000000000000000000143000000000000000000000000000000000000000000000000000000000000014400000000000000000000000000000000000000000000000000000000000001450000000000000000000000000000000000000000000000000000000000000146000000000000000000000000000000000000000000000000000000000000014700000000000000000000000000000000000000000000000000000000000001480000000000000000000000000000000000000000000000000000000000000149000000000000000000000000000000000000000000000000000000000000014a000000000000000000000000000000000000000000000000000000000000014b000000000000000000000000000000000000000000000000000000000000014c000000000000000000000000000000000000000000000000000000000000014d000000000000000000000000000000000000000000000000000000000000014e000000000000000000000000000000000000000000000000000000000000014f0000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000016100000000000000000000000000000000000000000000000000000000000001620000000000000000000000000000000000000000000000000000000000000163000000000000000000000000000000000000000000000000000000000000016400000000000000000000000000000000000000000000000000000000000001650000000000000000000000000000000000000000000000000000000000000166000000000000000000000000000000000000000000000000000000000000016700000000000000000000000000000000000000000000000000000000000001680000000000000000000000000000000000000000000000000000000000000169000000000000000000000000000000000000000000000000000000000000016a000000000000000000000000000000000000000000000000000000000000016b000000000000000000000000000000000000000000000000000000000000016c000000000000000000000000000000000000000000000000000000000000016d000000000000000000000000000000000000000000000000000000000000016e000000000000000000000000000000000000000000000000000000000000016f0000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000018100000000000000000000000000000000000000000000000000000000000001820000000000000000000000000000000000000000000000000000000000000183000000000000000000000000000000000000000000000000000000000000018400000000000000000000000000000000000000000000000000000000000001850000000000000000000000000000000000000000000000000000000000000186000000000000000000000000000000000000000000000000000000000000018700000000000000000000000000000000000000000000000000000000000001880000000000000000000000000000000000000000000000000000000000000189000000000000000000000000000000000000000000000000000000000000018a000000000000000000000000000000000000000000000000000000000000018b000000000000000000000000000000000000000000000000000000000000018c000000000000000000000000000000000000000000000000000000000000018d000000000000000000000000000000000000000000000000000000000000018e000000000000000000000000000000000000000000000000000000000000018f000000400000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000022100000000000000000000000000000000000000000000000000000000000002220000000000000000000000000000000000000000000000000000000000000223000000000000000000000000000000000000000000000000000000000000022400000000000000000000000000000000000000000000000000000000000002250000000000000000000000000000000000000000000000000000000000000226000000000000000000000000000000000000000000000000000000000000022700000000000000000000000000000000000000000000000000000000000002280000000000000000000000000000000000000000000000000000000000000229000000000000000000000000000000000000000000000000000000000000022a000000000000000000000000000000000000000000000000000000000000022b000000000000000000000000000000000000000000000000000000000000022c000000000000000000000000000000000000000000000000000000000000022d000000000000000000000000000000000000000000000000000000000000022e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000024100000000000000000000000000000000000000000000000000000000000002420000000000000000000000000000000000000000000000000000000000000243000000000000000000000000000000000000000000000000000000000000024400000000000000000000000000000000000000000000000000000000000002450000000000000000000000000000000000000000000000000000000000000246000000000000000000000000000000000000000000000000000000000000024700000000000000000000000000000000000000000000000000000000000002480000000000000000000000000000000000000000000000000000000000000249000000000000000000000000000000000000000000000000000000000000024a000000000000000000000000000000000000000000000000000000000000024b000000000000000000000000000000000000000000000000000000000000024c000000000000000000000000000000000000000000000000000000000000024d000000000000000000000000000000000000000000000000000000000000024e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000026100000000000000000000000000000000000000000000000000000000000002620000000000000000000000000000000000000000000000000000000000000263000000000000000000000000000000000000000000000000000000000000026400000000000000000000000000000000000000000000000000000000000002650000000000000000000000000000000000000000000000000000000000000266000000000000000000000000000000000000000000000000000000000000026700000000000000000000000000000000000000000000000000000000000002680000000000000000000000000000000000000000000000000000000000000269000000000000000000000000000000000000000000000000000000000000026a000000000000000000000000000000000000000000000000000000000000026b000000000000000000000000000000000000000000000000000000000000026c000000000000000000000000000000000000000000000000000000000000026d000000000000000000000000000000000000000000000000000000000000026e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000028100000000000000000000000000000000000000000000000000000000000002820000000000000000000000000000000000000000000000000000000000000283000000000000000000000000000000000000000000000000000000000000028400000000000000000000000000000000000000000000000000000000000002850000000000000000000000000000000000000000000000000000000000000286000000000000000000000000000000000000000000000000000000000000028700000000000000000000000000000000000000000000000000000000000002880000000000000000000000000000000000000000000000000000000000000289000000000000000000000000000000000000000000000000000000000000028a000000000000000000000000000000000000000000000000000000000000028b000000000000000000000000000000000000000000000000000000000000028c000000000000000000000000000000000000000000000000000000000000028d000000000000000000000000000000000000000000000000000000000000028e0000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000520000000000000000000000000000000000000000000000000000000000000052a0000000000000000000000000000000000000000000000000000000000000521000000000000000000000000000000000000000000000000000000000000052b0000000000000000000000000000000000000000000000000000000000000522000000000000000000000000000000000000000000000000000000000000052c0000000000000000000000000000000000000000000000000000000000000523000000000000000000000000000000000000000000000000000000000000052d0000000000000000000000000000000000000000000000000000000000000524000000000000000000000000000000000000000000000000000000000000052e0000000000000000000000000000000000000000000000000000000000000525000000000000000000000000000000000000000000000000000000000000052f00000000000000000000000000000000000000000000000000000000000005260000000000000000000000000000000000000000000000000000000000000530000000000000000000000000000000000000000000000000000000000000052700000000000000000000000000000000000000000000000000000000000005310000000000000000000000000000000000000000000000000000000000000540000000000000000000000000000000000000000000000000000000000000054a0000000000000000000000000000000000000000000000000000000000000541000000000000000000000000000000000000000000000000000000000000054b0000000000000000000000000000000000000000000000000000000000000542000000000000000000000000000000000000000000000000000000000000054c0000000000000000000000000000000000000000000000000000000000000543000000000000000000000000000000000000000000000000000000000000054d0000000000000000000000000000000000000000000000000000000000000544000000000000000000000000000000000000000000000000000000000000054e0000000000000000000000000000000000000000000000000000000000000545000000000000000000000000000000000000000000000000000000000000054f00000000000000000000000000000000000000000000000000000000000005460000000000000000000000000000000000000000000000000000000000000550000000000000000000000000000000000000000000000000000000000000054700000000000000000000000000000000000000000000000000000000000005510000000000000000000000000000000000000000000000000000000000000560000000000000000000000000000000000000000000000000000000000000056a0000000000000000000000000000000000000000000000000000000000000561000000000000000000000000000000000000000000000000000000000000056b0000000000000000000000000000000000000000000000000000000000000562000000000000000000000000000000000000000000000000000000000000056c0000000000000000000000000000000000000000000000000000000000000563000000000000000000000000000000000000000000000000000000000000056d0000000000000000000000000000000000000000000000000000000000000564000000000000000000000000000000000000000000000000000000000000056e0000000000000000000000000000000000000000000000000000000000000565000000000000000000000000000000000000000000000000000000000000056f00000000000000000000000000000000000000000000000000000000000005660000000000000000000000000000000000000000000000000000000000000570000000000000000000000000000000000000000000000000000000000000056700000000000000000000000000000000000000000000000000000000000005710000000000000000000000000000000000000000000000000000000000000580000000000000000000000000000000000000000000000000000000000000058a0000000000000000000000000000000000000000000000000000000000000581000000000000000000000000000000000000000000000000000000000000058b0000000000000000000000000000000000000000000000000000000000000582000000000000000000000000000000000000000000000000000000000000058c0000000000000000000000000000000000000000000000000000000000000583000000000000000000000000000000000000000000000000000000000000058d0000000000000000000000000000000000000000000000000000000000000584000000000000000000000000000000000000000000000000000000000000058e0000000000000000000000000000000000000000000000000000000000000585000000000000000000000000000000000000000000000000000000000000058f000000000000000000000000000000000000000000000000000000000000058600000000000000000000000000000000000000000000000000000000000005900000000000000000000000000000000000000000000000000000000000000587000000000000000000000000000000000000000000000000000000000000059100000008000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003210000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000361000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000003810000000426fcb9639d15aabe6d792e23ab12fb9633046d4be6911a60d64471d7560d3f6809143b7d4943a3485115d37e7596938a16c91b6055f3837640d8c36b8303bb3c06fb5fb553496e5e0b48834087e036acf99d6d935dc2ebf43c82788cb5ed1c6a2f4bd77ac2bb5474d48c2856135d18168cd6f69f77143c60b3cc370319419dac0000000000000000000000000000000000000000000000000000000000001020212121212121212121212121212121212121212100000000000000000000000000000000000000000000000000000000000010404141414141414141414141414141414141414141000000000000000000000000000000000000000000000000000000000000106061616161616161616161616161616161616161610000000000000000000000000000000000000000000000000000000000001080818181818181818181818181818181818181818100000010151de48ca3efbae39f180fe00b8f472ec9f25be10b4f283a87c6d7839353703914c2ea9dedf77698d4afe23bc663263eed0bf9aa3a8b17d9b74812f185610f9e1570cc6641699e3ae87fa258d80a6d853f7b8ccb211dc244d017e2ca6530f8a12806c860af67e9cd50000378411b8c4c4db172ceb2daa862b259b689ccbdc1e005f140c7c95624c8006774279a01ec1ea88617999e4fe6997b6576c4e1c7395a22048b96b586596bd740d0402e15f5577f7ceb5496b65aafc6d89d7c3b34924b0c3f2d50d16279970d682cada30bfa6b29bc0bac0ee2389f6a0444853eccaa932b2a60561da46a58569d71044a84c639e7f88429826e5622581536eb906d9cdd25a2c0a76f7da6924e10751c755227d2535f4ad258b984e78f9f452a853c52300e212d8e2069e4254d81af07744bcbb81121a38f0e2dbed69a523d3fbf85b75c287ca6f33aadbac2e4f058e05924c140d7895a6ed167caf804b710d2ae3ba62b1b51297b3ea37637af6bd56cf33425d95cc5c96e9c2ee3077322fbec86a0c7f32c15d2a888c6cc122e99478c92470a1311635142d82ad7ae67410beeef4ae31f0902ba2fb964922a4610bb18901f7b923885c1d034da5769a48203ae6f0206a92855e2c01ddb3d6553386b5580d681b8230fa4062948668f834f23e0636eaff70aaa64519aafdf4b040bd2f9836e76b9dc13cfec8065dcdf2834d786e06260d10000381000000e00000001bc00000090b1c43c1dd10fa62d46ac721c5529068d286a18a86e22787da7a222925e32a3148915e9b0ed2f8793823e9b4fc52cddfda38739e4a23cbae77fd06d498b9b8e6ea0911e8e23b97a17da9fa3777775672dd21198963bc0bbf6667d10365dd77172004b20053ff5fa88f214c7cfbb1501c5aa7c9bd36a08bcd318a70f71ef4a5837863bcc1e9a539584952bc17cb270e2d9000000907686f00e315ea807d51ecea805379caef8781be53347315fc9510f3e9db44f3e65862c9d964b0007dab956272d11e50d1338bcbaac47886108b376f34d207aa5e94ae1f2adbd06d15590d0cb7061bb79b839a791f90fdfef054e878748fad2123fbb481f7e1d9cfdab406f5e8093badb645fa2cb49203e1bf68f945ca21f5a03027dfe828e99ea4b6cf8ae2daae54bcf000000905510d0f861d5cd19326c399727f453486bdc7b85202ecd41f5fef58b5078e887ec5b91f4d39ddbe24e2233747a1e65e6737b58c0850e148f7e92cb21e947efb518882acf619244aca195b5dd44fe507cf4f7b1aa9386b7904b01769ffa8211b3ea6ab32f2c85a4d03c6266632b602c655f1dc01662d283f3fb700a734f14c580bfc7a5d2228ff1a4c7761708d9322f2e000001bc00000090c6d6caffce822bbedfa8b303833ce3bb9b0b3a79bfbfcbfd3b563f02298c592c4c7216a38ea06d6e9a1a75b8ce59730cc56c8488246b4d5261647058936277ea7ca9ec6df4bfaab432e580fb3ee6ad005ae36548d2fb1e7edc2f1228431bb1acc1e3210b4f8d5b68647dbee0bb7924fe2727a6669cfc46cb82fcdc1c77e3a0de61eb3f0a7da43811a067fcc07a5303300000009014237a82b5c531c5b22ebeb4f7af578d7998537fef0b53ee2fd725d07550da956a9ea3ffbdd09144a539b5fe593a138d39e93551ff290cc899b250940749ec14f745945d067438975a090af541be7ca68c7acbec35e623263cabd630ebd11d6caeefaa085a44d44810e70f8d6a631a8207ea4aff253f47613ca512bf04b63bad9dcc861f61d81ed0d7a88fe0747c6027000000908e8e062b17d6f040bf583520ad0d92e257880d51cd470ca10fd94d0e03815062d26f1169b1ebaaf9b5dde8a5638abb39043bd0e31d0939415ed88682e4b1d0e434f879f11542360439caf4db74632afce8378f2faf1316e528cf3a64d7e7e3a5fcf322938c266d48181fb71b956c6f66353469056cf26e8fd84c3ae60f72ffcf1f9deb3363b93f5ba1dda798c93f4ff5000001bc000000908cfb998afce6a15aa54c86f27370d5fa09e60944ffd9ba0cad5da227e2df3e678226f78094eac382cd0cfa9fa7dadf181c190002fde911048c253e2f05e8267af3129dd8be984d00e9ef152762ef161f93fc72ef1ac9bd0de640f6930a88f1fcfa9ad5bf5032505cb5c90ad5a7b22cd0b2c8d9af4f224955b28bed5900a852c9418d94f1a1e968afcae8d489895996a800000090579b53b18d0bd8e4db71db680804471f263423a4c57a5807696dae3a7d751e6dd8c1340ed4e0bb63e6e45b1fa36519d89f164f4ffaa41d391d4adeee0d02273595d6cfd41d0869677ddfe43cf8d4fc6e711d983bf9064485083edf3dfac78ceddd10474c1e07234f04ecbc50cb1bac7c30248d5bd9efae2bd72baad5d108f98de87b6ec6414d7581031cca18a7a1f88d000000903df50823b273945486cac84eae73e266121418fc5756bfc8ea6809d69410c76daee250d34b31a846e74ed1e9b87ad3dcfe92600d5bf24d620319554a8c67b0c2a76816d6bbd0ad08d83a89b121a4209e751b3e1d2ab0ad916fa673ab94ae1ce7c5367d77a7e5094a96e8c6d85575e34ebcf5ccddded1e82e329b0f5d0606f0010754789ee4e8be98adc2578c242d3eb2000001bc00000090fb7cd18168f47d0ddf433e52f0ea9f4f1d391b476226feae60b6258e7e9203983154c17120ef162fb83d858ed5d89acfb48b5b42ceeaf4cc45a3a2774814a2bc080b2a5d139aa7d33d54730e7c6b72b8c81475d99e430e914d609adba55d572d9fd9a2d0d3b55765aa106faac79fc50ade6e09d2fb18631a6e5028dd30a03fd78756608d3390e38358345734691e130400000090f354980488624491d43905213b4f502c0e0cf43c88bc840c0cbf1f103e74c47c3bd8bebf1453aee92c4ea82615fb9f4a50aad10dc69dabdb8ec7c8e518f55628f48a22867302d0aedbe01330ef940b87fcbe1522609073955a1f4e53b580d9fb5947abd430f3ada138bd38bfe014bf017d257cc3cdae7ad84837ad0aac1b8fc85d7f619247c58c4b84d96bb47d61447d000000909a34e935d1b48a64b74a4b7a130471c3197ca84342093096b7d7a5e57bf4aa9d89f286b4a27619861589c15f9ff4ac6277093e2bdb8f1418ce45c88b1b94d69c8fa5194ad9bbcf79de7ae6596d0f81fd1549667d7a97d3ad96e21fa687964b2d29ea50b7c0fff8bed8e3f5df7c0b7528130bc9443be97e2cd56bfae66b9f471572223673a7027b9df9c8275b7bf3cdcc000001bc0000009048deb9e0401c49c61deeea50914ae71c267a5056dcce4406b02478aead4616303a6d36685b3d23d995d2a3c31dfcd58a4925ae67251b98bc7a08521bbc80b850b7c268e8b796d8e4436e156fd1d9f906c508132999f3928da46a5829c3ef8636ce949c5ea263c6e0a23427bea71a8fe5cc35344d0e9d61b2891de94fd71fe97e23fdd6a1f28937cd6a4efa1e3282614900000090ef21002e4193e387593866f9568792747b9dc5353764bb3cfc9691068cd7ec63509277384c3e12130782690775bfd7598b30130d4797603c432bc9b5bea6a27f0c69b05374e731c220ff281450b2d4e126716e2bee5436d160b2f6731bf35c900639a6d7e65330b75439d3c7c6245f876e2950cc7fa970ad9ff16ebb6a0898525dbff3e00564cd209f260c2bc4f694910000009011f59e99b692f90fb8cb1a1724ea0cc8585330f95afbc3393e707c4d2a7703cd6ab9d975e56c7c1d10863b3b139958722f4af95cfda6159bca44175d9b5c6ac481caa2a86c6770abb6d6925a00213cea8455e87213b575da93ca24ee48e627145db559c962519911b1afb7a47af5ba0460a4a97ba00bd4bfe9f52f194c5e8ecbf6427cc4560227224f38c07b7e59a4ba000001bc00000090375f55f2a7cb6057626722e28363fabd45f30ea1babf12ffe3184303eb5788010d104812cc4db5d7d18114007d9f4314e64fd02acba6c5bbf83ccf2f35446b21a1e7955c55e6859fefe6f71f2e16ddf4ec1ed3c5178daf2c9e5613c3e9aae3201946fcdbeb0e8719b01a18a25905cca216f034c4f94ace0ee4bcfb8bf0b40ff0cdcd8f1238384e28df63c1ba65f91b540000009025e941529ddcb4ac5e93de566fac91cad1baa6dae9cda86477c95ce97c7fa81b6a3db36b657711ea0a1a7669a87ebb593bae554b66a058af1a21ecf1b2c5aecfa191f54733a32480d29a1ba026b6fc90b0ee4e718738ee68c2b34e774116f045f1138ed8c99ba88b98affe4806efa98b32435d368c6f34dde1fa09715624ac0611faf13adc4a8a6eef04646897ce61af00000090005b88cd11607a66bd9fdf06b8023ae16072df1d1a7d223a6d7d37049dbdd417043e11e1736c01af9098b6e30ae6e59e1882deae1521bd41b011c3f18fbb569d20e9af66508f743059537843cd1088ca526dc1f174a8018e467dc19af5cfb12d20d09664ecfafa7b1acf78c1396f0dd20f606f27813b004b81f347c313fcc77ceb5fc33758c4042e3bba26e6b4c9ac79000001bc0000009011e23f2328a3cdcb9652a6dc95eca90584fe46f92b70362a33f8374faf6c26f54650df91601eaf32e44ef840c5c3e0e5d02e51198bd57920a5c8b22aa96288c430ca5c462b8aab462c9222c36adb884bf5149978707ef563dad2ab59041332b569c79cf44e915c5aca22b75ae7218e996bc17d8409b0cfe5ea948fa2f95162b00069e3046acb65d5e245756709742b6f00000090193875fe2d757b5c35f529652a27b43f96d9866935e92806baa042413da52f6785a97ffa06741bf7f3a52031e8f67bc4ce40528f304d1e9ef6efa19575100fbe49f2147d7e0ec69f0f232c239fcce61a50addc45875278d5ac24e389521fca0c6a49eda5f3ca7f5cc7f1f9fad15332c9e6548f77cc0fbafb72413f6af7a44365b67dcef769105d6fb99e6acfcb8ec3d80000009060f4380337b2cf5647dc40b9d4d198ab5c8ce6317e4ca71b6b120612539ddc0de66f6b5a42257146ce190103849efa4dbc68903f1a32164258c81ce43caf8b53aa73519fc442f2f93cb9494fa19ba62817d116704233bce8a761a13221cfbead9bba235b6567045072eda0f4da79a5abdc640ca003cc218d3ad51fb6f5699ac03207e37e457d997288c90e043a4b49e1000001bc0000009027ec222ad55c14e1b629cba9e7b024ad8b045150f409c624f1b4b5772994e2f847ed4c6fc9ba0b887fdb370dfab6bc9a029a882d513e848d0a000e6a21d392081159e910e1824ca8336326c6fd32e62ee4fb91d3bcd1ea95316ccfd428235da330cf4e74853790219bc1d868b696ce9f289d416dd11908e410c00a7a2b0ae38ac6aee983af156f7ab445944bf3c78e8b00000090c7b511f4186d7293c83edbc937e81e2068ccb3253c10908e1dbb877098d3312ff806bf187b9521032fc91802a275c546310d3ede01710cadb7fef5c5ca3f188c900120b753a91fe69a4bd6ee2c39bed71c7ca4d6d5718ca20a3c83795545b169608e6248ef7b16e892c08bdfb2e261f85863b80f65540b1a68f1fd872fd2a9012f645a9177f656ab4bf297b5340983a500000090acba85789c0bd050a43cbd36f5b1c1e427f898fcee8c905e59e58b0218f7f652f7f947cfb947b52336439ec95ff4472481c871ca400dca0b90bd36ca63cc72e884736cae8a5ea449e383aab49c4cd6fab9c185f5b03b2bb9995436b1736674b1a1f8a9f4eb1102ad1fc406db1efa35e57d04cb2b69521ca599c548a3545805d44835b5dfde8b9039207128eba0be564d00000e00000001bc000000905bd444a8cba9ec75f404b87314bfd6020f57de5ccbe53d5fc09d4ab7d0523bad932cc48f4547c19d6fb6e5dfd506051992fcaf21fdfda48c0cbc1bac1a0db7bec9bebf7c40350a275594cdb6c0f5c94478f2d8ddb378ee5553051ba1014b4ef11c72588a4add965476d0da2f6d92e8c0745301d16b48600e1818114f9b02d82c8fe9ea5faf6a6033431267feb6788e3800000090c2cbadc76ca5c5af8dcf7288d4ef510b6114c120dfc23294df68240116291024a307f69ef01a033c11adac13172bbcbd83499538f279d4e6979184bdb3e9a71dce200df699cad843448318fc765621c6e762e8115488774101f7e3fab9e5c3119cea5b3c016fa19e2fd7686d7ab0bae4a036236ffb0cd9a1de8adeb1b624e150e917033d9a6aaaf2a93533d70007bdfc00000090328f9d32f22edfebdb68b5243666e71fe47a0739ad54826a2c9022076c8ca9f6e33deb528608d088e3e1d9a4222f1155caad9f3c4411bd779fe4e48a416cd6b30dd4dc5af43781989d1677d3c9422ee5687fef2d3f2de01d28800086c3cdddd4fab892dfc81ebdc8538f69c21137b4edb06e3b57ae6e75b684510c05225cfaf9e3ca6523fa821c3769d41a21fde5552e000001bc00000090fcbe73df4cbe4ce643bbee6919bf8ce36bb0083e91ddf443c4ee83374ceb33a79ff29e7155da16a7e1a4cc387b97981d6c38ca137a53c59b9e953d528df5e966da7e28dd7aa35b22cd75f86a920715f03dca7ef0f24bcb605e4be105a9b5d5f3fbe87f5d8bc8536011782da77732815fd863e08348fe2eb2c550f8bec4410d3850a79fd9cd2e37a1d244ba3184a99d2800000090859704b25ed494302d05adc7012684828a21a45b8aeb93df0a2d1ea91e312d66a2832a040bd2bcbbebaae7cb9dd3aa225d094a55977583142576a401ad449ed0593e7d9fd1cbc37ce036af276f565a038b63875d8090ccff076ac7495b4ff9631e86a6a0cdfa74e2f677750b1a1c280fb6ee6ff516163491aa744aad3a1c487cdab16dfdaec960b66ca812785ccf1f2a000000905726064d12c28f46856076461dd95fef270926eacd55074d64c998310a4798b699e8c864d863fa24c164036b379429ba81e7b9b52afda13d16cfcb231ff6e36812b16064122a163cd7bf935ca76ed6e275cd0e355aa41880a54c6f4fc9089e66837f477a5a0dda745ac9196f94fc8e1ec36eb8ea932eb6b4d737d6d005d0b8e3f299a2c02ac881de574900c89eea6339000001bc00000090bfee39eff3bae13492bf9ce976a56c09f166dbcd46ece93ab636914d02a7392596a2593c973fc5329d8dd03aed494ce37ba3a9a8d44108be1ff4238c414f2d8b9de63fc3adbe43edfc0bfcd0b2c85b9752eb49a51f36b995d20a3f7d80b35c30097ccc4fbe50cc4bf627e51771b5abcd43806c5d9dbce141c8c002bd56ea73d3cdfb523d1b6195ecdab57b427096227b000000900296ad69087cf87ef862839d9581a851861d600fa3e1e0dd4eea3cbebb18b868000356deed62fc7f1645f1a0b088aa169164b7f93b1ebd2e82485a677b57b68166d7896b4c65fb4808eceecca609f95546159f1d2b4ebf30862f18673f1d0248025bac6c928438e32c3b087abe01b8e827c811178866b40b4e3c14f8d1b6e89949ba757df7d18c21dd7a71fc5d4efe13000000900d33920df26da7d1d6b36377566e86e40a8015ecf91f88630ed533afafbf02a9aae149987f96ebb264eb582b1fb1aef2dbcaccc9efd52801c7d4d091b4c4a6e1378ddadfee155b09597a2ab3b1901780f0e606f6e1ffb02decffbc61f6ed141c282122b58b6ebd5807f3005d365eca15d2ce2c6ee04e53f0279af43b59017f3a5521ac893649bbf5e44cb9831879a7be000001bc00000090cc65151f7ad0a4bf1b0355424b24cc6eedba544cd71860bdd03a6855fc8242a77f21ccd3ee05d414c7cc8c8ac06108cb6102327bdb2a50dba83060c70b6cbcb31c817da3bade2a008761a0055374adbb54e0b549fe8ef201b83064196cd04bc0df1500b86ba47caf85b8bf64c01e93f27290ee8f0cfb6a5a53d9e6e2e092642df9d33d21139cceda22b76cfcdc0bbd3900000090378d2ba4c3e4b6d7f41c3e40cf644800141153017fb22ed45e0afc3f3f33f26da0a42071f04441b98a1af1bc41fc67f46ec17497f1ae5e10850c11452a284825a40d124958218ed6ab40f0babea0a827dba3a9e6efe5a9ebabbe59a1a3d64cad6ff4d706d2cad4c3f18964bc3afa143e8eff0b6469324ab2f417a4c1508b917a3e87c2f826a2ae81964f3a2c5af72d81000000906cbd3d7a20ae20388791281bbf4e67beaa96f258182d43044da9f1ebd6887b1b72da20bf46b67a23096b3f8509177b9c8dc77556cdbcc916b1ed02737ce1a8b34d3e74058494773916a1d1ab7f6658b09bf396ed9a2e70ec021178f9078dd57ca020c7a36b4e75548658ac642118f8c74062ad1169c47c2ff445433a50ff23623bae3a52b5a614fab34d12eaa314bb26000001bc000000906ea8321846cadbbbd83f29e437e2f68ed651e32a80b91c23fa9aff1116e3f7d084d238933ee634c640a369880b6eb7e1df8dd518e82546a53657a7aaf8f8e7ee8fdabb094a22378a14da612310e7dd6502aa80f268fd9bc57345c0d62c8b5b3dbd20c652445f67444074c186de015ef73221e6e865007e72082b14e9a6b03f76f5eeadfcbb705c1b03d6b983e8be0a28000000905219224f11bb74c2905c836b29a468148d41fe096400b1c4a5622c876d4fad2c161a13fbadb299cb0274f3ebb24f19f994a0fc82dff8523587cd1860b97bdf73a8a389a8b933a4711ab03bd77b28caabfdcfd2ab79cc27fe4e347d62b7acc92d385c206e4964e6252c4dffe60cc0bc6d9d0e265237941efe40116c02ed8dfa3805a63b6c998936e0a6e3d220bad3ee1300000090d07856ff8c6054302a935ee722eaf3cb1b9ec87e1c99ec4d5f05b82035d25abd2919636567dff299030db4df879a1e3da982747382e803240281fd86455d3e0ccec709c94a3226ad404ba79d6548a729a9c9ad21e1070e32154c4bb5665ea38ba1d7cf28598b3c115d50283af0a9f83da97a6571fabad795c7da119c7332ce25f686ad84f40b3ef6b08328bdb8e4733e000001bc00000090d3f985d89d0e61756a2139c09fd5502a712bf54663733ddf69cb1d3101d85c63924c625b12cd3bd36ada36864bc4443899d3c0735a5c2a29f86e8c8673952bd937cc39a6101ea05ea7e29d5ca473c8c05e3d3e37a9cb63fe19a38b2143155a2412b7feb1edd44b9686dc1179e04157e5965199f3d597190cc205e17333c995c32d5875f222e11759ccaca03613f713410000009094984b569bb94bad9099f29b90ef394dd454e2af7e3407a242f48bd08cd3fc111f49fbfc8093676a0a0f958a44b532e771e676fbcec59a5d965a98681e5460260ccd9ff9afa3638d92e4405d1f26776855ed9458d250c2854550f2e7bc3b2a7b1d2e49b3f08100ce724b420bd2f3de6a8078622e00fc4616bbf50da777426ddb2c47dc704cfcd6deb695156ca4a60e8000000090beee2b163d5d4a655689e8b0c30c8510d5c9e0fc68df0c94e9fa90d88a01d433523495024f2bf686d72323d34d8559aaacc1806e7bcb45b51cf697e3dce63ea0443807ae95619d8cbecc598e60b40ea544e93bb21b8d5862942774ebcf4ab1f56f42db3b75e3bcdc61e2c601b73286789f49ffd8b16788ce30dcd556aa35cbf32e1f628ef27cda313b1b779afa78372b000001bc00000090ca71834083c2c4a2c84e8192dec2d6f869bb192cbf4abd9a7f4fcd832d27875153abb7cc2da5eb8ccb6915b96a294cce6e99ba7632ef6757d7008b0bf0958d11e1bd49796e191a0c0c8417b4923da5c0779aaaed17ad3b4fad14065fd4203d73375e945b7792260c28893e89e3474970f80a2d5ea0f524f3efb5ffa8c2b895cbd32c5a77595c55d0b02133b97519f24600000090de3733625aa8d0451bbf8ceb0be90358fddd5863068f18505e05f9620da1bff7285b56740cf001d503bc05ff9e47c0769b45c5066c9f521cba91852d64db3748354ab1e9d12dac0284d0b63545918f00f4f1bc14c98760b9f02a666ae4721eb7b99254922bdc980ee12674075da138d68b385df945e3695c337af5ca590026d81bc5170366d9e1ee6e1a6b964782859a00000090b46b4bc458ff824b02d652b9e0e7d785589131d3359e69893c85b1606bf776de78b58044d0beb789dfaeb3104c8321ea76369c5db074097f126f8b0078dffd3ce005eb18a09cf43ac4f6f1f38dd58d1bdb1dd0dae627b5528d10adee3e1a822ca5c1aa8489c0f0e8ecdec41ad817ee12d534a241f7a6a13bb0ebe6a0e6d6349939669d5bdb654bb8f69d111a941af156000001bc0000009047099475fc1633753f715412724344352f1e4e82ce39b92b0d12b252413edde0e7c95f8b3b5ebbde7a57ba3da250bf51b2cc9c8c3f29e8b50d29ea0f31015879ca8bd47f6e092955c3c45f028b68350001831603834529b9e9625fee1b2a212e006b853c3ea4fc9124da15fd610a40aecc48728f1be8e8db55a32b26de66d1790e404c95710c76baeba3e5912a53768e00000090bcabda0ca6aea4f9d32bd42a74401684c5f46a968e48f394fb82b06ed1a36b0024f41ed47e948ddd2f96f0f9e2df0e7e61f67440a2b6e41c59059f2f563f27b21b4a7960c6f2238faf8d3bae1b31c1ed8974932b1a4ecec12e11772265bd9ded7b6751cdb0ace6d0db1055121a81f8f39b86d4e265c2bd82aa63fa52135b18c7089105d261e8a7147712b67a4f41cdcb00000090e02c17c4eacd5cab85df9268d835e0b3c1210fb114be217c839a764f1ac161bbdc2774a4fad9803a4c99315afc07b84c3796c7420f48c4a69ae5cbb3fd637a34ffb4ad7828fb81961d3bfcf6f52530d0b53f35adc9f88c96612123a23a011adb9c2b8e9acb53ee608ff642307de4ae094c859489acfe3c1d85119c4a786a1af5a3efafe2685aa4747cc63fdc84e9146500000e00000001bc00000090538a0441399b0eb4b37558ecf90d9bf9d67db754fda7d60fc3deb3c4e767bcc087f8c11ee7ee94353bb1a943dc8d148f97c3172f61121fab91a333f97129592b891c45de2c185d57c1548022605fed55bf47dbbe5470fbb7fdebc757800c35880c5ae70e372cc46272caceb0003fd6a46126a790e8d65baf954669f71bd817bc0e8c9b36e735963afcd3256ef4505b2400000090bcabc8093838df793a13c3cf44a1a70fff0569e2b1356a210fd55913b23068d1a98ea92dd3df55bd2755d97b09887edf8860b310a675eeec0ac1869aae6289c3e49560b8f4eb583bf40f6e21654a6b80e7af429fc7f038d95ecffba457ed949bbb7995158b2061f986643795c68b7967facb2cd028641daedf89779cf3fdfc90d12865fb851a5cf4eeabc7911d3d8f4a000000901f9dadab38f2ad1c8c0bf1b02c9bf4d584069a3b54a92ce07141c19e7bc975ea943719224e6f93fe69dfd882b6ecc94ec80a1da4189443cb04636e92f09f88adacea1ba91c31cfb9b7474ff1d58d63652f522adf727c82127393617c4b1530d8c961d41e378e3f5f266efc1bb0b7455d75148d6c79f98c01a5d5dafd3ba03f2639b4b45678ef60587bea187af77a11bb000001bc000000903442c44f4115857ef498fa03b90666e4b043398d13f0c2523a31c4ad92bebb6b6a3826f7f9158e1610939d54d9a3a041aa38d1192bf0dc797710a83799f48e470deeca42989c44b7622c4b99ad4922673f9ca98815da551a3d3e882643bdfb9cf0987abc462b859b433c1c7e1c9e96b4b4961302654f31326b8c6ab0b751d6f267c2adcad54b9058cc6fd5e9c8b70cd4000000906d69f738576233ec2328d6bd1ee76e6b40dab296dc3b214e02c6418e007ea20470acd84bd70c7ba5c24cb4360c8641f399a5b928077b3d11ceaaa73c11f7f37bd246208926b9c1b0e55abe71413511c4d0f04e811246a32542f7df6ac2a53254f1fcb9dd9716d85697cfe7afb069c65e9c03247da7195e9d39cc81f3dbc1a5b78e989c9e6d8956376e65a68b15be17340000009076c552a5d849a23c028a8df9458c56ef39d43d4e88b83cc86a7c5f93937b9c4db4f58496390d315c983284b9b68b5a546ec81ad4276d487ad1327ceb7b1f028887b1a7dd91fc6df07232e8371d78770a860f4890bef4d66018bc2dd5cf19c19da34b5efceb81804574f18011c9d52373d6d4996811bfa4ac2f947d90f5dcff96c9a8a4a407950c1de8c24bfff838971c000001bc0000009020c2f2ce0288f6e482241ead06ee7eb7b9f7d75f81c08b0da7b8d74fcf71c226add5b6db17fc64cdebd52bace97d20a655a05db3e73cfe16a0b88f11c049bb4a3b84ba4b50ccd39a9bde94edda541f5b44fda6a243468e74b9207fcd9d0385417891fe89f8c98a7ff77709e225c2039b6669e9879b5f595f52b194051ffa1a28f79973c4766e14da62a94b96483ac44200000090899898e94b501a777a9c12fb71a41e7697903ee2467966a5fe4ebb3024763e3d1ec29fee30c778929cb1a8a7265bd94d2dc67b1f6f6c8fea74ec2218ebcfaafb86f31426be18238178d23a165e671a30f3cb1b583f31555cb2be7e939aaa3a5b730c719fc73baccb83f69c07b26787d0107c7576dddc9dbd9d5fc7d0877f5150b27379c1d0b957180db2f528caadbc870000009057a22904be7a901c8cc3780b0277abfd02cc690199f208162df307f4168800bfbf2d997b9164cd2094a905d0d990cba8bebdfc6b4f30f740f811b44d203454d4364376c90912dc515f7752494649674edce66dc97a66dbb635ae57019ec8c46279e5373b6acf992a1a49edfdc3975f89d3185ad1b2de46ec88647230df94679cc356cb7abadd931aa6f294e34c3eed16000001bc0000009025609637221fe1306882993148602ceb784483a1151fed8e832fa18354016cdf71254410e38ac04184851ef76e6fcc5aba4e9a6c9e2638e41c7c5f8cee1b13cd3b0d17035b91ad04c63ee8a196ba537c26145c76a657e3d58ce9fed1d815a923033f7f3b4e866678d5060c1fbe338741b642162e9b7b8f9c47652521db9d99936c4d1ad38ee22f40bee9bc0f9ada6616000000901cdc562761ae6a5df3735ae6520eb47449dbd832f71ed2fc601aba142d20e1db7af47e209133de4a4874478df07071a4e0e373f4c2a61f0ef9036ac6b9f7dc2ca68a9b9d42d95caf623a8d1cb579b14e8b7d3fb8364f41bb945b63d933917fccec8de9746d5a295470a0c72ae94f47ed1a19445e70fdb586d9e6e7040bda1392fe9f3824c366512191767ddb708db86000000090f1a3c9017a4f4e312db8be2df5c2ffc914976a896d42ed510c47de5e437d6a4d7c61f893b78170690fad42b3f785fc77aec0a3cfc350fa0fb8c2d167b83f3d6fcb68e198633ab4538cd326e50c60d09f11cc14ddbf310e66c54021708c305bf57fa089950e565ae42169fbae159a661e68e641373df3cd38f455f3b8ab7d502d8187451fbe918b56a7c826e97ece64f4000001bc000000904576be0ee1fb3989c2b50c3c12eb035478869d0070c9522062b4d729485a9d6ca310cb28835f7611f4c44e8a92bbce8a7b52e229ce919a836b1942920d895205469b78da41f4862da83b05228c9728fcb1865b5b24002d2cbd16df45312f7dd1321943f1f7bb6aa432bdb84462069bcfc58650c1b4bdafd3992b5ba65657965656128fe027ce897b2051c9c697a1bcfd000000906898e921b3171d11bfa8902b63cfe050b43129c7395b6408415a18ad43c9543ed00cf3932f6023e999bc2ecb19853b3f20cf8cd5dbbe8514d9ea535dc4767cc9c9e88ff5bc664641e6fcf7d60b4cda224238e457073e702b2aa03c6d3dbb3ee797493a3afb2d079389e1548b761aa78929c4654c1dc1260d1db3cc989eab174cf2639838f844efcc4f648ed6b1c635f700000090fc9483293459d113eee0962ddc86156fd3cd45d05a0c16e07b4c0f3bfa660e3b069664717211a3cc3682945993548caa5b22df049a2f86238b3118ea877ecf9829afffab51135dfb66651aca3346892221585fe7b4c05b112fdfeee3134f6e8d3c7205e0de0a7d11fd4ba79ee612e840d6f859a4e3c746d122efd2e80755b46e4acf8c38d23f9271e101865e1ed823fb000001bc00000090c3cf4166479dca196f8f0246666941e3159f4e630223144a4e7a5a3f9f275c365e9191c212e2463a159e9de3155255f0f9c3b26c82f97c1d82601be3c51eb33752533cedfa1df5a434d37565da7aeb49f6fce23d4419aa3dc6c29478260e4254119cd7fbc9734d2ad321ab505d3b3c81eaa6e991c9a101e7250d71acadce5669281a3586b524e89064a42ebbcbf9f65400000090f32756a1d3fac1cfd947619de20dd66cb5a3bab4a4b1735d81e0b70deef25533bd6183cbe0b46ce35a4fa3ebc01b62302bb65d2a6db20233b500577296657e24768a1837c1eacd099834e69d3f878ce837dd31a7b8a8ed78e624a1810a7aba36f6ddb23c411b2ee5034a366c18bfaf3bf8e32e6d38361c9150f3c008825b8be8a3e5d177c80b2450c4feffbf29c8bce500000090c815b26c6e93de18db7c07eefdd72c77fc04eede275c87032768403812324fb8627805521f427e1fe5f881739df67d2f94fe358fddc4a971495b8ab18dfc8bcd83acc0117b65e15ece82916332c400424b9df1a8c6faf744d3d858cc63d5748ef8e927b7cb58c0129426a11afea0bb752eba612fb3f3bc43b1a2c627e7eeaf83e67f54683a0b4886990b67e07ce112b2000001bc0000009047403c11095d5b37db843ee4def2f8314b6f35746d30dbcf7095a8f5bb372ba1f385c21a8261210f8d0ac4c574f48bcff6f6f328bd938542b88fed261a7bde161f45913a788d2e418e9c8e432edfedd0147966d3bcaf0b98d0df08552403e92fffc01a0508721e1ad14df4306b4308dee4de010f603093bab5faed87c6cf60ba3171a00856f2ac3b06b699f7bfeb90f700000090af5b01631cce3055d7a9453decba284c3fdc06b0b71d98331cda285eea5206b3d2849aa349f8964e41cdeee6e5033639273d9a84fd20d695347a217822773890dced57219cc9acd1c866875d58939b7436db0eaaae1e44ed46ec19aa52c5f07139084a2ab1750e25c4ba379c4e683c08dbd954495bf6290e6edc61fdbe268142910e82c6aef3873100e3e7f40c4e856b00000090eb59cea068d5d6f4ee1d8115de3caa9ac0bc1ef647dab13a897080ee608c4ac251f4ef3fef30c12a7ac13eb02de4f2a37027b245dd9f2ea62465039a847c84c9ce2058930d1fa6d573866a018abf06350b09f9ad6e6fb0934122c64e7edc06690432f62faf63bf11f2881d3026ac9bd17d2143f7bd55cecd872e6fc98494f20c9f1d3c6436a4eff1baac096c0c5c882e000001bc000000907a519a60fe33f4b9382b5a67a538ebf6f53574dc1b2226f682bb87090ed48de9760c9b3e96ff6c87a8d2f2bcf4eabd7ae4d7f7859b10b3490561221bda35379a6196f07f7eec1be17be8abf819fa543655da0b0e224a2206197a464dfc7f0f812f194256ec8197aa776989f7f6b373a3b7a9fdbd40f85b203d63ea04bb972dbfcd15e5e7f1107e6270662260771da70e00000090bfa04272dd685cca97b73fe43df6f13aec32a46040e7a6495aed0c841aaeb495dc9428024abb90cb8309a9bc381a08428cbce5642ba0c18ba4cebf593102a05c62dffebddd38699b7d0f61d9f0114e3d7c713d65175a0aadbd4ad218a71bd8c9c9822dd7aeb4fc66994e2d31402cf81462f3ca7dc5df16a2dd15326f212096b22b30f2e8634327453802da76ac65615c00000090a479c98778c84ca2d9882cc42ef49d8441e855a2f54a43e045ded34978d82b0b1b0cd89ae968660d697b5873cb35dc91d8f463c2607d6f8848ce86a8c9eb3d3acb36d049915591056ab1b79aae00a5f568dcc3c48ae34c054ecdbfed0e96eae51e734386b262e62de543bc05d56251cad682b889103e58a536508821f847033a321142d8933ecc097dcefc5430fadb3a00000e00000001bc00000090b7968a955d2dfccf014999255b9ca6e954d7caf9a303bef2814245542a14bbfe846c946b03c54c945928d7c18b4a756d3636260d94f5d69ef6f93c523b2d80e2dfa6212f9fa3c82b083f3cc5d0a6a500837ecaff099978ac1229a823df5b11facb2eef513c1b277d704e51c28551814916d644e8818d8f971e1d80f2f735644be615e57885d8568b3efc13b4a9a1fb0a00000090a10b4b7824af15267906d782f4904863afba1c3d1ed581e99e32c6c493a7846e6f846c84286bf333ed45ae38e0510284cb164ccabd2fe49d5791a710b94c8df61b337fe569991dc692314e8eca72af12107c2d75080debacdbc1318c0a8c40b6cb600c366a733fecc7f76543c4dd73ca4b237ab3b176d98537532fe0382ffddf4e4426f5227b0a4ac756c9f6badb234e0000009085c8b49b001dd30b7018b2a4a90e8485c200113f9c3c7867cdf5a4cbfc8490f3b34a056c35be01b576f39d69a86e7f60c2f6f8f1ee445b96a4449114ea6ed1e8d03fbc50bfc658c43e8d71dde864b506380ad15dd48cf99dc7fd3c82f3f1e48bc4409ae2ace69c4350fb1235f7e445b1f41e96477784299439dd984b19d8bba4489aeb66862d543ff340073d046c80f9000001bc000000908e9cc39a7bdf0721646ba3b0233c7b8631903679258eb14f27966ecc188d74c92fb176aed4fb55e0022bc417a1976da6b1e9ef8a26a9ebacc234bd030c9f930e09fadb0e5989f964ac2db2e8c08fea5ce219ee1c757de3b3305d537c5acbdb7164e6c0c5bdea99ca6e05751c3d6c8ca4abfd29f3027cd21467dda03574adf18be31acfb55227f08291fc7ff94aafa82900000090d7ea79f2a79ac7b7199608412594e9c39d6d200c865056541c65d00218430bb6eecca91752dd462ed04bf2337c17763fc18326195672e7664138f28f44afe7bd8ee5e36e5237fe193ef4c82c8d0c608bc078dec1bf9a6e693113e6d91e261c579c66478ac5a1596ccb91ed884dcf88ff8b8ed010c46d26b560d54b96eddd6b4f435b2149aca948f24d9403096efc7d7000000090cf657fdb9a0dd2b397c3c6f86cabe903860f881fa3d787e00179173c8061a7a903389c4f0ff1bac518ddccf514eefc9814f49f54ab22f25cf0bafeb92589108ffcd6c35ead1950640949e8f7025f5e3e669dd8f298498655d4f22d4908c40f6a1c9d02ff790d397440b7d0e0e64b2eb03b52be821ee39dad03788d613b876fca3053ce1fbbf61588be94342740011cf6000001bc00000090db066dd99708e02662124ea437c46cad412577c6b6e94d535f1bc556be4d2215c19428926ffe706f811af6eb239dacdcfe6f2b00c14c70ccbf2548cd3eb3c59ee030c1a0b3f02e952e3e370f39a3407711a393569c9905fe0be6048cada151dd5fb1337d334b5dc1c2b018eaf892d3edf265f5c22e45dcf843b0647159f4dcde84bdf31133bbf8b7c692b7d24c1a063d000000907bd9616b869597f8c331a4112d51429c804370966e033be8135121beb7a4dfc21ecdc37facdc01ecc6d1db6e9435ecb4833f197b6577403d57d2c09f511e496da938ea4898bbbeefcf5c776112961888d2aab79a214ed7e45d34a7699ad5523e02ae32c96a2a49c0cc5da2581e81ccf55439f4f5d0e52f5542c86a65dc8c30aaa326972fc852c514e57877fc1632a10100000090db2a0f48ef0ee0a981673c0fc77b364c4d5b02498e71853c6806e66f215613b266da6f2c3295f6a61099d72f94ceef9a0ceff8464c2b9b106bf36bf6ccfd512f56e7d026e7f680ae933978974704a03b27a1ce316c14c50bd191380b7ae6e5eed923737aebcf01092047f81ecd47c9441536c5a4579dbb32a1fa3c2d5758014750efec3a27c52ea30b68f5d9f4850e2e000001bc0000009028a58a7daf03e720795deb55e471a44b048b694f524903dd1aadc6779f6b286caaafcfd0b95d213eff8ebdf24a227ee8a2503d9c02ee079bee47cc032af93c1f513da5c754599e5c14a53cce674e1e37980840e2cbfa446d7c8e00ac0797cc6973c723f3e7e1e4f05eeb27463c1cac066661b441c36d43d223effcbb1a7e16eb9a616c23efa8643703a519fce0e3dec900000090e12682942a9cb9d8d97e0d8542d4b3eb2f61e1c6b18d15dbe382472cb8eb601c2191b239f98b6de85ff2d96b53c5158e33dd6d195bf4837dc481d5c068136ec4d31923c522342c81ff3fac8721a1da2e83d81be1e3d657a9d6ea592a634ffd4385226199dadadd8af222616ddd36b8a58b6b014d8659e76e99a4447a1773869f57c4ff69075ccddfa46de2db7673102a0000009014acf34b96562f756f3a34419fc01eefef5123364d56c35c3226813f56b7868e300d8c299ef09be1b08b38a85f759bce2b14fffcf6892f37d18c54ba9516a85fe9ed9de7293d7e7dd0a8d79ec9b069faf44f250c4c5f748b396901228c7bafcd7c89059fc0b330b37a2b9be7c4805c50c8cdf439e0f6b8fb7e8706ed29940bf7098edde8b3f427405865d2a6dedbd040000001bc0000009076e1ab6c68777495c22513e219f4a9ef4ddff594c95ad42337177a1fb4842ee0494168c135904a0820e65e95ef99548b80dacc87fe0ad7a58c67076640d3075e9f9f1cfcecce95b7d1178fbf6ae8662ac3e09c6470fb6ee972e1af3833309657eb02185552813b4231fb0dfef0046567243efe8bf958502aa87c2b08463f71ca4194bff89dd4b29b7e039784145d5e430000009041331715e1cb585124b0dc39418125664fe058a973203758019fa2ef0ea59c7bd5699c0d072e61cfade043c16f98fe730b4c8b62c66aeca37cb5914337462d7a8865ea805c3a989f184a154b8cb49305d9b15d98703e9bc55b33d3fdf0ac4cf476ed841c65a6372744f4edd93b550e48b49c3acbeea01235449807c8bd5edc26433d94016700d07365769ecdce72bf4400000090c362e0f4934778f4585405e6402fb9de60210e5f1209f942cf428510da7f03774f0121a7259defeb3f1ff6895b5dabb8fa7dcf1c7e1c8f157c01f00f62968a872d0b1d5d072d37aa0bd644d63489d0fedb5eed785b537c852f81b1def1b4dc788eb6d8255e396854a06fbabe3cc4482301c38923ad0be4f9696227eb3f5d83e99f554f1dbd79d1b64a50341a85a73740000001bc00000090a6e13c2eb058853e0865e0d942ec7d63cbca97e6aace8d62edb8429a5a2cbd2a2ce68508e01326c60881c584e71f4eecce64d2d2ad804c62ca47c5a0d1958b6bc7983aae29d9430914e4c09247d53fc8a1ecaa20b93662f4020cf7331a55701ee9fa57b97c9044d7048dbe7d1a9a350c96e0e22b1d808e8d24f17e233e0c864fddfdc7b68a8877c73cc191eca7eb9f1000000090922b263725ba81ccd8d7422243bbb9806961a47e96da934a3a5fc4603e5d5e4e6bc7d38fd31c84aa1747287d9f729869ab9c0f32bde16677534a83148f9c489920d3b29f6cf68324c2e5d864932dfa60fc97cbcd4d9657281712b022e29f5f89992cca034a6342d635eba4c0ad2df5af89904e4276ab1da6a39432ee1c0f9a8bf0ee4eacfa26a54d1a22a3d63732744a00000090c673de086083e4f2deaa6a3f34a917e374d1a9c2577ab6a85ff9bb583bbe7fa923f3efcf68348028587ee4b7be6df0a97c6af81b93af58038760a59f2d91bdd3126fdb44c80b208862924a53122a75d0d0a70b5433714b1eb1203cbf165a934a6568c68a22d358490deecff0558ccdb30e006a98cf07610a55194893c9649b30398a3301e8ba5d721279caa106549b6f000001bc000000904a4029e9184666e191b436b44bfa3a8857c4335075eb18bf413257765e02e3e16fcc66d3c2c32e339c0ae1b4f99a66232adfaf33417d71e3fb35cfa8272ab18257d412ae7c56b7c65e1dc1c4ffd6ea5bbc3f16dd46d111c38999b5d4ad141910dd482765f3cdf7d6fda72159ecf246f4ec5e09abf857d3627a4d90553dde7ea3f5663a02b08ce0069b78cb5e4f49c0d8000000902178902bc93e04b3f3a6658a3ee3cd0aef2a16aaed6f0249aef5a026de6f516849832b547d9a4e026ced2da2ede5ab517adadd045cc698446827bd8bca4491e2521ad944320d7198e23ae65432a90c51c7d0c0c6dd2bb78ec5a47c241ab318810f37025a301961be34aa75889f13fb67f39c272222d68fdbdf47e0ff5b3673b290951c674a463c68ca3105bcfde34ea600000090688c993c3b2a470cc31c4bee959a98d1af8c12d21e520b50773fd7723d9d9d6cddb095a99236d054ce3c11c6f557cfdc79bca98c54964cd68944b28f65831eb3ec6086b226077d5512c782e030b57e3c3276da66d8eb3af1b7d2d76a2a382a1b6e7f71e1c4130bd24b864dba04727ced8abfff56ad35533c1fc7eab7ef08949d08c5208ae3e042e763322cbbb0c65776000001bc00000090d50be3ce4e58c66a007f2716226fd00515d21be6161efc71317f535c0ad3f1cb0497aa0c55e3bec7a34675504334497b6958783f1090b88ee174e107759ae3706ef200b69e4e9681a67239f9512c4e86b101df5bf73f08d3fd08acef16bc9857b722a6472a6dff98baacace6f3478fa363e9500406db02772c4fd328f7bc82b0448cea431a089f6f567410d44992c3d100000090a2f72607f0f9161f24cc9c00ba8914937e24cc3d20d3b14c751076ed5481930c339928038c366f5fa1db32c055782c94b44d7e6a73c72b8370740c9a91972eb71165516107307e18f0d58c0dd51fb0585f05e6b64a7903f7e427ba93bb69a705fc464095757f14c5b86bab8352b84a0066210a190d0ff68fb88794730e6d63cd8ef03f632f8a6d28e7deb8a9df7dc83c00000090b5abd77435f66a78e2de4cdc07f5326e6ed20a594ebb0b3dbecbebcddb6ea6fd756b4393b024cff5cc9a178ae19f5088f6130f37117b37bd1bb5847f352862590f5a21dd0b1cd4060af8dbbb7300557e12bbd00fcbf953b4111477c5add912c77cccfe78306b3f29a5186bf4f005d948b3eef381081e2a8693299484d28445e4843b48c6b52582d8a2b5d84c749ac950000033a000000ce40000012800000090229388b3a431be6b9653aff04b9359a8608767dc2de0419efb0b91f247a4269f45a4b1e2a8bc2e6dda32f707eecd34cb3366b36803954fdbb8f279d852b37b33decc47345388d527433dba1449c00d03a490e6b5f7f0e9ad28301d099ac3492489f8287165f5c231389123b86db7baae55731f7d2ca674378556f89548ab1f9f037b5c982c7b7ebaa74ae94e2dc4732b000000903aee5e522b5f56e536df8ef54bd19f1c5bb1d820658fffd764da995e584a6efaf04d744beeb6127f19410af6fb65edd2261dbce88a18cf55d47a07c5c550ebd1d6425136927d40bc78a13c45da9e88993ef764aee2a53ea05dca3ed57368eda2c5a6a13b08687360271d05d835b94b78efe6b60f2839355849495a363ded3507648476e19e3cc1d7903191e5bd8598a60000012800000090505af5b4991216986ea5634f906556df448f90ee8d7c400a6e96e2d2014d9ca502059dbc82a4eb40e6422cc17d2c59399035a13cf18494af6e79de74dc243f69a18355d4ce14c1121ec65ec9722c4667e31ade9bb4ab5c41db9ebac1f0e215dc5236dd747e0ba8685ccaa12760865546d52a9e2245d699c8e88d89cd183e04c0ef8b4b6547141d247937a6da6ae338e50000009094439dac22b0f4e1a1cf2747ff890355cdb876d1133463d0f3016734159d099540628b8a81562afd699b1bcbc2885ef8feeed751536d9bd8cf046a0bdf0398984146839da849ec6bd897350bda300f04276a1b7d9abf799776a3c64294b6e4c9c768e19f0ef758e0fa97a706e6315cdc7a2370f89e301ccfb5664a9cc5becdf9bb0fd015936b984c9b4800e62b2e1f6f0000012800000090e0698c6a57315526b74f6cb3617711e0c054b0488cd2b3d413e18674be991c4ce9d6796564eaebb8977b5ae75ba9a47259348b64e8330754cc5543c3b867da1709fdda0a509cf7a216c6c8693547cb4454d17181ad1e67c51da2446c1b922c87836775ec86ed5aba1e4e14bac831021dde12ebd15223efab74e73b47d7d42a64b255dbe1929a91438ee5d82ec2c2b60d0000009072b35250299df92ec033153bd0799eae0234158b942abf7c23df05e6c60809a5e0a344d7de0b739c3c478c00c4c020c9cade6a7f3c10031c47ad3248bc56b47ac7ec58f071aabb500370c990804be96157e63e41ef9476a965467cf588f77f2bb420477512ff881df4bc89628bf8be5b095170733398ffb463ad6347e4ec5d3345e35be599a28d5e744efb625ba90ef400000128000000903c56b6d74c83e54735baca8111b0c3e5496c7cc3737c1cbea76e13761c0c086366de8290722cada540d19e8d45235e49469bfeab2b09f84607d6bc069409fc29d6ff6986e715caf2b411343e1b4e66c0aaec16ff15335f3030803585f8769fdaff07f9bb95d5b3a1f25a3d5bdc8b8420d6ab8d8c8c3146e82e330abf83ad8bb8e8153ed842cb780af11f4a7104a628e000000090d88f4ce50221ca7385fb78a7db3e639413b03b803b6bb177f100c95007507af701f9b4982607615066f02f598ed06b6f98be07c33e8283eae9483624822dcf36400e8193dfc720d12e58d57bd1eb0fdaaa014be61644f62ad2401ff2460f91b9a852ecd0c761ed2fbf6c00e721cea4f4387bc825f1bb92d29dd01e7aa31f4aba59a2188d3c2590b52cfdd921d7e8e57c0000012800000090aab78c0b3962855a0dce967579343c0137331b1cd622dc347ba3b6e3d66ac75c602484fb7a24e771f9e1b3f9edf768b86614c5e12f5aab14023a1f35e7693c048468d51e1ad2f71bbe06deb0a6f25b348372c25388d1cd7e44fb2a01194786bd2661abf38e323be07ac7e7c869348b6a0fe03fe712c3c2ba944353e7c161715aa4f5a6988a0531bdb757a5b96bdba738000000903dcdb5cead4dbc2d6f7f4dd0093b1c14f44876543bf016543b15a05938d6fb66f17f02a0e5467532f8441e0699e20acd7060a2b6ebbeb04f65af86b416b9a081a5cee331691a934379450b2bb4ceb4b1cab8f5fc861ac3d2acbb6485e2e689c91e09052ff61f9a819bfbea097cda0c6474f8a788a00cca8d4c863738bed83bc8e16e0e9759f9ac190d2b28987ea4231200000128000000903cdefcfd45b1ba2d83be0a46ab7dd242f7e5e895e7672f50add523a0eb6358f94c04302bf690ba420aa42a69e82a4f373f1887e7698ae51d11e258901baed21b6c0d6c3d16de591910540b9abad5c89a96bdeab09b27a752e94dcb2e71ed8571c963df9afa3d8d736b0621d941429139aae3da4fafa8def367250ad17cfaddf758914fd43d6901a92473fbb27726668900000090985b0016d8588b238dfd905cd864cae91d3ef4474833887e1a0119f5b8e67441d409af7bf106a2a9a10b887bf8edaa4d240f2de9cd0d7a180819412f076d2502431195001c2cb2b624c249519933e40cf7918e9d8856a8bd927f761bab2a4d2bb1a8292f4d63107f806f7fb971d4e3dbfacf5798444c335bd737a5e00c78f148c754f8d761c3ff396af00e6808fd01f0000001280000009001ab2f2f96fcaf3ee139547dfcb20712ace6ae915f8d86d2aeb96a8efc356a2b0c9e40fec9b001f22ebc49c53056f781aa0145115822ed88c143f12a353d5f0470fbba36e85ef4491c85c310feaa6a20093eaa78eb1e49c815cca3fbcfa32a942f90dd82ff94fdb74d66a089fe8529fbbb6046b30533d144f6a537fe8fd734ce32bbcab3bc2019aef32f77692397b2e400000090ce9fbb834c6c927a11fac76cdfc44a00edcae62a5d8088092a2e7545a3b4a7d6d58566a30760b7b3f4e58fbfe571c4d5c72da8be7b7999a7c29c70a02b868ef5558b2b70b1cba583d27898bbd2d66c2923944c85d4fc77d36a5241780df91173f2bf13e9c814f68c1fe6b5d92d46f7c1b2bd1e69ed00017262f1d8b1fe795b498b920a9be2086eb2f00b8f652964039d00000128000000908860e1a69a9e2cbf88e05b93915ebc98e8b609f41781ac97fafe6c3e18e8c276e6faaa4b252c784a9b8c09e1caef1265e0f2c467d6272fdc889e539ef0aede3684937782e4825d9aecc975e09770420f04ba1333c84fee4dcc917d6ee0e567aa421f205839425a57597de409eecaa93d3a4fb262f9a99c14bfec03e1437b1108323ee30fae221f925397481c6dadcad200000090966fed22f972cdb59d1aa2e6c28c7643e2d1a2dcfd4636345c21e9d2c702dd6e9c97eea8fa2b4a5f2ce409f9706f1c4f7061ba0597dc1772c5252d3f5c0996e17fb44db31970424fa04c39e4f9dfe1df3a19ef6536cf3678704566cead85577908d2920d4b8bdc43c8154712f0e994deabb13034ec5db65ed7f1a0099cce611f0005bd6b2d2fb8168fb09b296d6d3de600000128000000901e24bf1b818df3b4f0c786e0f65346a46cbc6e32a4b3dd7354a3e49e45474fa4e90e150e00d9caf47ff6cf2ef7e5af39576ad7b7afd93a1271dd3e6cd08bafc90cfe66772ae41fcd3f6f184fc55fcb61ee464c44442f22a5d6ae4d8423fbb03d2ecc706b6043e1762d51df7c4f72e4bae329325f1b813714178ac415fac87060cea5fc7976804ea4d580b5a6a9f2bc320000009069200cddb6678b395690d22a92613469428b1d487c79be4450ceb95273f6b409c3dcfef361f1a0fa88209a31eb26b67cd96374df6bbe2b32dfcd867d00d8d5f4030e3091d200e4d95a1c0d511f8f37ba31378e1bae21b3125818a75f52fc89fadf49b10125af4c66631914efe6b68bb40d1d6701e52cf14587be5e697aba1f2427d67356e5a9c10f3c6454ed23af43f9000001280000009075bdd58d142542120664c3a621de692476f985546d6e644cc10fff950d99e39c3dc88d84dee487f947bbb0f3917c292f4276e9f87fa5724f0733daa0922f338c1cf4ad40224cf3ffa9e0ef29349903fceadf23ab67bdc42a6d97b73bb85352fb539252d59b6e7e86238327d630e0a4f6ec9d8931377580c4f6ba30f5187e4d59f60f1acd6fc96ecbbf94f13a92830790000000905ed953ddd64ef5fbfe5edb5fb182de41aa7a1fd5f9e85b6d5ea74cd8a7bdafaae31a434198d8841c3718a07ce17edceaa7e3fbfef7c0cc8aa893fc8115857754053d8c77e1b16cf1feb8e3d0860585641d188ea2c414831f3624ab59d835851f1bd033090662fcf445ba08f4876bf71590e5bc3c32ceb745088cb0f2ef46272da82ee42ff496bffc2ac37a20d2bacf4e00000128000000905a341b6ce0ede8f5f8f6e8d32e8022c11d0c68fcb7aced295e2ad1a83a9596187a46d257d062ab1c70721d5aa577dfa429081d6a355708cf37b903ca19764a3c0dab929758e5193aa8d12150c059339aec63bebd51c3092d43145cfb9d3c451dc39d6381650d37398521efabadbe3c1f182848defb521b96f8010d5ebb0d567cc1a35b1f92239a47976e2b32989ca7d30000009073ccdd690f4eafe2eead77e9e089ba7511cae490f3538e6c84934e714a4e9fe512908ad79cc5ba5e0fb79dbd9e583c32a9bf87ee48c96635a9a75be8c35194ea95499f3249d0a1bd568e081c6784f79a20467f0d81545aa21a7e38ecf6ef9d94dd9f5df93bf563720b644bda89f3c1e229db4cbe6eb0dfca5adcc805e1ab68da3cb812817251b02acb103aed40541a2b00000ce4000001280000009026bce16f9034980ede54249ed45fd623470304ac7d5c290037b743d785762c0178dde6a5b11b70f67ea2aaa4bdcad784ad51517c0aad8d0741ee9896cdfddf5efa8cb971bf13704a62ea56426c5b5b28dba5b9d7431f3b703d16ca928b14717ad847beff58bd00703d469b04ae8d5673c764459d7c70c487e2b8dc56921f3772fe890e19d46937246b6ecb53f4f50c5500000090f04587d4fab3a75048c04fa486941e4e874b19ad77ad780f6074d2384fefac7ce05c28ae2ad4bf50e0025bade67aae8770584ba9992b15db5533cf3b89fb64677adb54c862e350062e733f35edde61fc884a5293d24e20c4310964eafb3c5960d7d1def9a6f609e98b1022a692f0fb786d81df0d158fff49bd11a990c3122e2fb83684fe6b5106cfa2733c6fab9fb88b000001280000009035a7eeca40c8e2bba2ed2b757b8d6490f44e13e161354541e0075ce961822a4eb27b051457f9e88be8684213c8fcaa00ad2052fe2886e701cc24d8d6f7a4aa4b3597c7d85613edf04dfcada7d7c39da86b077d97620635b6dbd29824e0848357be256b504ff5652165c4822d4ad820ee2c79467b423f5d31adb633612b5e5b15777d6ccae55ec70d99a6c10c030cfe2a00000090aebaa1ebca372bd33e7852db4163446d886090bcd05d6e5355d11047e601463f4507859694c8e30d11c9d74acb02a5c7bd5eed8dac2d9f0e5d0281007233a02ea4c0ed665441aa57c0c7082b942d65b53e605ec7d3d89af83196c02996622bd3ebaa5d54b7d4da94727478e2987ccb4bfb81a153a99603930fd609be0e6be983e1fd59f539a91df3e9f1763142f9a6ce0000012800000090524c471a92b6d7165edf28ce0e54d66a6f61e270e338d4cf262c8cd180db7be85cec5365b6008feb5be52c99461328e1ebdf6893df6f9808a74e2cd5fc6c60919000a617cb9a4bcf84e01cd6404e7d5dac142e826e737dc8758b90c4dccaedab94f1cbfd7cf053d8fcae94bd309f201831e0edfc6ae8e30457e0c0371031220947c78fea955d4680fe39ae790b9e963e00000090d3bd94123ca96bff2dee2aa370efcd404e118f1180d79530dedaba3dc3154614e9915a8b0901b005c288f061f03c0c60566d357ef772387d8e61e8f4c2b8654c62c7cd23c3ac9c033d98892b1ea04ba5b4a9aca46eb7a128045a09269f9bd4fee8402b8be68dab85fd929afba6f29a7fe738be2ce0e94820e0f9c6518bee1b44679c9780f200b5c6261802b4a0be2b1c0000012800000090319c0d94159436bb81fcc81be0aaf9e3118a5955348108a8fca1f4a77806ec0462f1899c78170ff4176f5e97d5a8d2721e7bc03a8eb6dda0351850ba330a2b54cb7128286d9ad0d7b58c37d112b2536db4d6a86d66a3c8559d4cdc19caac191228d72367ed35f94d5d2dd7043ab074a7ae22ffb167347637c3ba2ad3a0c419d1181e67b061a1de2ac917f012999b1d0300000090065f2755859b1133d853b59bcc80bb73af1189664b3195d624861c07c9eb878cfa8687cfb82c4f851b0528473e27bb9af77059ae1c4e23a5f2372ff75857075a753a9c75875ae2129823424e66d5d78f84afd052d61b0a1b31a3dfe6cb1a806aa19a50d5b9ec157d2facb4f563abcb83cc3b6aac3dcc528c329b4919a355907b36cf2ee524d926ba87092509e3d808f70000012800000090fa2875f8be9eb0776642aa5d2bf2bc615e324ee515e2d4bdf81c09de95cb6ab3136288cc91a3e145bd70fc686df1754f7162baf30e724733a98258f91792da9bbc4264cca247c12d94e1fc2f2e45f9f931060c14c2462f6b0cfaa1a6286c03d3e30d81cf3c03f68af6d107fd42cca7438ece9749058fbc2328dd9bbe02f4f8d6d2309e6c3b0001af3cc932dc8bcc8ed200000090f50d7d32ab91cd8c3aff752e922844a0bfd5edef2f6bf8784360fd5b486e44288b07615b9d580f34a302222225ceb2f04d41339ec684e624163d4917bc8e42193fe14b0c1c88d68f88f9067ef677bcb76c752f326f2e536bf531c021e543fde42a5aa9e1c499bee05f6d78647b9d9d5ebbeafb53b8604767811f8e2ba029cb438e555810adec3f85ba2521675c2750440000012800000090d3e5f1f0b7463a85fb887c73b59cded50445fe4e890daeedf6a576380d2b3fa11c7518fb25af49490abbd6712cceb4305f3d43d61349be0c9b4c393a8a6bbf30f17dbc06c2103572b1b5cf22f86daf99eb76470d469fae219d26c1dbd533f47cbf1a3fdedbc55ae23dc2a789e51a8ab28eb2a0f292ed0c9f7253913199ccb2a910bb012a2ccf4630fefa169f661ad3650000009010599e417943025a7639267a1e18127c1cd439d05a1039a059f8ab49f9dcef63b890dabfa6096ec7fa0ddad844b29842ed3ec56bd6190c811da59fa70a07f9f5571dfa7217aa63717e7e6409ed81e37701851638904cc026911824d6b25c702fddc156b58f5f48ef411176330f9fae31236c67f30a1650158369ffef352e4ce3fc6a4d8cb5fa6ed9c5ad9ad3d119701a0000012800000090a389db0fd184113a4e4b46aa17388cbde640bfce3325cf61bd8ad7cdad47770eaa77abe2e690f52076a252505704b309cb8851cb69b24c95a87489136e6f73c0bfd49b94a4ef197d01d754d1ede85e4663e923dadbb4a4b806746f2d1ed9d992e06e2dd80f320f827db44059e1716ea92a1fb7c77e0d624d5adc773982b6c8d6687b699b3bdcd74edd00d78a69658c7c0000009028f5c952c4c3cae898a4269ac8ee0a0d4168e771a6f8d58561fed45c016c95c495c721923916f6d187b2523f7df94e7db8342aaa50f7a8c76823499ece6d3a53b1e095c4310c0225f60b20a91b86f1d2fa94c0ec508cc26b7b88b01a4b3b17e06698d586af6478253b877068f6a846f18fb0179240bb56e4e54770b3a4bc215c2be2b50673dbfe7bad40c07f63bbeff30000012800000090f2c814af6271db7c8e9d4e66cde9621bf22aef0134da41f47c35f71e471322fd66ebb39b40b974ca386bee6418469d5a1f3614758edb5ce1fa9636e03092329d8974e695dbf86e8da247feb4968a7b061c729f685b9da1d8b8635e04567bbd2b33eba0c833ee0f0bed056365873336e905c137ea0f17978259c2ea780c8c25ba328af85d9d98a0e3d938ae4107e42332000000906c52a4d740a9a815af7a7e95ba09c78226736edb78e8802d418b12022485c3b7ce79547cc3dee2c8001c0955d2601cf7aea7fcef6a09f1e36410a28a233be825bec542c9dbb53b6b2d203676d2c62b06d180b8fc42125ab058b9155a27244b61552af9f69f214acff4373f901e83c64a49116c2dc7b9b312b4fc93de2bd45a90864fbdf25a4ff286563a0ddcb50ecc770000012800000090bc8e92635d068e8f45cba5df8453691e130a9fafddea39c56f766fc987ee093e76ab21755ac24471f4bb049e3e71af69d9b12bacd520d6541f74909f903d3ad05830a6453d20c79fbcfde7ea423693eae7176bf537bbabdf309fb4e4178f40ac2eb048540dd302daf6deeb69aad9617361c56304a9037a66d39a94c487235c862b95d933caafaf0a964204eb00b2fcfe00000090f1d1f253bb1383ea73d63955d73b688b3daf0a019a1747ece001a07708988e629e5cd0d557e8f2f674db96e904a69762d1bc2eb02083b535e7767d14ad60fdebc1ef938d8422cbcbf9042ffc27a76523f81cf17bbb658d9cb951fe0bf9335520c00aed26cc37b7ac1eebd297ec02e90deb147a3aa5f2933973c37738cdaa040db84200c040225e7a4d412cee477998210000012800000090b864adaecd36d70266605faf87d3eec7205abd549042d44f60ffff66ba0fefe5f2426826b44c2779e144f28a162d06d24efe8cf29c337333c8ee42bdf40e0f5f836da60d7ae07495f821360f22afa06e85fe1ff0710fe0f12f2e1a053cccd9a89eb8d69b12d0604daf20a79e2b310d069746d271cfd788b448aa9c907b0be49faa548bfa0eda227aee965c8f4b0cfc1700000090cfc24b1d4dc86a7e0b31a4b03db181ed811ec14b156df7da4bf44ec6a19d7154ff5b7543d4a117176f0cb5a509706a19736b86ce8027dd3ba62ed0e1bf93bf2686201a6aa33bfeac26fe49c2a91dedd85a8d0323668373868b40e65e662edd3b079fda7b450524e89ff5987d8a5ede5117c005777c90a426862f76bfabd4ee3f254525478d37403866cc1b536b24519f00000128000000907340b3e06f362d0e25af7a0054eaceb640eadc92c26ce2adbd225e40075c48adeabba9a0286360795e5aaf7ea15560d1e9ccb9307b854729ef54170a63bc4584cc33744bac67b66475067b808c7b3612837efb49e00909b129456195dcce20256d5aa533a5ccf6c81644deb9226458b01fa3882da3ccb1cde0778a76e0ef1de5a397b1a74d1015da520af795974fb68300000090d2f4bebe950156f5a477fecae6f502f2e12d0a6df4029e8b4bae461d325db1d3b5812a5bb055406757f41621c76865b80d6be58a8bc5d9916c0627eee5928cbd9531e7977707dccc5c37a1a8a91f61395a77fa5fa5b7a70b8e677d311963474577abb368e9e3e571a3161f82c4603086ee0acf8c450ac2390b5e11e41c161ed9490c0605cef9916dd7ab4dfc8639aaba00000ce40000012800000090e549acf82a2d39497468fb5b925614c77bfaad29c7c2097eb12481b38da2cf526d83815bf1098dd63315c17a3cadc92ba6f513002cdf6a388f1de593f4b1075fcfb8ba1775f0b9796dcca84eac6eb940a7c45a9fa6c123681ca3d9e1879f61b842aea625a7de75502dc5af86d6b159b7cab7fdefa98b2db1345d07cf7da54dc5b1735ea31d2e75bfce4767273bce88cc000000905b57379b21c844b13cf8f3a037091cb3d0badbf3bde4c962d6b9c3c859e71295b1854091dd1c6668566ad727f9020ade69e19d34adcda46d2c93260a54929ee78d0f54cd90deeb0fc3d5860b7cf373b1012cd224e4bf27172a5741012cc83dd2ce8afbb70c32e3728fe900fe16284ae8b91f09055fc133f32923b06fa3f594635d85f964e8592be2175673eba3b07833000001280000009068833df188633bdd6038b3056a6f3c496a25391b1931b2f7cad08ee450a9a2a319f9781f9219511d9e739d99a6339a386822f561ffffd4d03d7fded85c2aa60202b8b67c104a85455a269fa8ffe4aa104e510b61731cea00eef07a943ea86867635706b6dbc509ee7e710247f254ce37315c9430368fe5e21cb5868b0f8c15d0494baa5aeb5c6b9a3b2985b3791b9a6e000000907676fe247936a75a03de9df952f34f0e82cdb40a1b057f932f6b913d30f708119d8857a1048511ea14e1710db2b90dabdc1351d9521f5f3064367a1d2db14fdb33bdd44e155cc39f61795f024ef460b307624c7a5e004dbea07c89cd271f921a21d225bc1cd87db49e398e1e8aedd923c9032f07b7e29cb1ceb462a59a6b2f9aae7cfee6064ac65578fe46c69cac75ca0000012800000090811ed27dc7ad47725b36f2c99ddb7e4cb66d6ddbaabe12556ab48b82678be51ba35a2c8401214e1c52e839a8a6903345b56c482977ab438bb64ecaeb5b6dc871e3a2876cf72333935d688ba754f397f3b96c795a3b292eee1f0def244ff2d3055cff950f727aef6d10ee069202325a8e154d526e08bd9dca4115276a99642612af60bb882d18903d261879dcb83c388a00000090e215b3deaf1f80a0450ca99498b82978e61011cfecd2b62ac1302b54f5abf8353658654997d2d66aef07ee1cb22bc33ac492274e548657b9d72c33e15883ce164fb56df05ab50d4cabc5ec4b3e328e98d595e36b1b778577cd9f07aa530d8049b4e39be4470cb7ae5afda18939496c85969fc1d3859992dbaf71a57ab515f4ce475bfbb014d1a3b005d698240f3569680000012800000090393392d7b7dc83078bd03116d5331cac8598a7d3dbfc13828d4a755de00f03b2ca88481407fbe04314601bb9001255aa51f43898cd2eaf0a1c3e11987b40fb4ce9d81f838de5c7d1ecbb79addcf3e27662abe805244c5b3d6fe5bd6e542eb329f48584e30d09d103679f52268d63abcaa6d02a0e9596b1c5f4d54c4fe11d8afb084494242cef2761e6be2b26c69cf28200000090082406fba8c9ae1b7c48b88bfcd627113f0d3f8c845665439fff90506e6d10257cdd5039c84c2b726a793c46fd7e30553bb33e2b4508d626e3d1deb2742683b26bd96abc118606f6fef1e0254203743f7ba5e122cc28a61e7c25550ee83cf7f363fd0ff7ba950447f08f868332b7369af8dea778aa4f2bdaf19a6ea422f16b9b4336b90d597c150712c3b083fb5dcb860000012800000090566d5d24fbeae58464b0615822f5dc1a5a03e6387afc0f89143738c3a7a82202b4d56c2bfc68175482f4cc3fdee16e023931a4dd760b719f0e31325a7198dacd88e32019efcadbb2a52b9701d23476fcfaff2b0f621845c3e246118f5c45e2a6790536c3984ae9a5984b938c3297920d7119a80e9e65dedb85f624888ee140d33fc9acd649e269a3ddb1f6bd31663220000000905b94546562731f1304d142db43c5462d32c17a7571b9a3fe2fd8b6c594b07a1bbbe4307e8b5c8c79213dde97f4a0ebaed453c4e798137e16b7d97c5882bf505ab2febbfd0a9bc802ea653874f11d6428cbee3a13a18eb54aa068cf2493512935163c59abc5d41900687c0222c084e85b476cbe9e2c1b367589a987f8c85c6b31257a22cec6af243663028f6d04b83955000001280000009047f97e0eac0aec21000ef407ba1cee999045431c0b8dd55027275e04502ab79ce4d0b6e5e86e8267a4e0234e029b335fe0a5a425638b5c07cdf37819afcd4b1f89246b02dbcd453bd7c2b0b25334f835468e6898a5c6066e62b8d869892dd294e05c83441841902532b4b5614c537bcfe557e329721078343e340bccf1abd74caf709ba0764352531308a7f5abbdf36000000090538969d6d762b76699c38f562873016b04d775021b4e9d2968430a2b3037c9cab3ae7814fd6dc7540e1a776bc4b75ebcab68a5fbc3eef095bbd0897517073639167327c8ce14e0e383c48475123e958c0c547ec4a2304df37901c05672f909770f02994c6f952eb5f21be1782e8f9b58df9967dbbf40306aa622ae9b4f8ef918105bfa6490e6311df30d9caed97e65180000012800000090ecf13baa64eae5d4ae2a2171abf58b0316e596ffb4e45d8b04fc6b3ef7baa3b6f144c69d47e57f4ce71725c1d23d904eab72f1e9558b38a2ec2e03490753746866158f8b5a1c1676b6c104795c2c72b46c02a0a24b09b0a167cf793e6d3a8fc4966487e6b9badec4505406c897064d16397c8f9c19e3be5bf2b6d836598a05aac481b44c2279db99967c7af13f9b754300000090ec3107180cd218bea3427fb271d8ee8dad175dcff2c80f1d9546beb9e3888f3da8632ce4672f5a4d826d219dee2704ac726a25cf699bc587432d1e76c5370cc090646ae5eccbdd2fd291b96c28f012c846308c58341f691de11365856500c6ac11ff5a20b28b39590fa03cb51730afcbb5651820568c5287dc99816279536f9016d8d770bfea03cc7dab39d4727b62910000012800000090b10f42fe05e1e1db07bac42949c8ba753f1c1185e27e061e5c73b8cf47eed8f356f38a8253891f322bca6bdd7d83933291953bfe0f225edeca42a4903bbdc73484d2b13347bb5b6ed7f48bb2215cb7b7cf968b29972e7ee078af4c634100613d21fd151fbe189aa19b395ee32bd671fd7bc3d1f9b8add290760f8cae20afbf91262267fb012b19527b11f94a35b5d8e20000009027517a0b48a659823f506f85de46054b9e586a0b9ef8de12a8d5f126d849333211c3e95f572cd162217fb7512eac7a87e0cb71a81c004a8d33d97e85832c1b7d33d615aa220cddc2ae5389bb239b8e29652b02e322d20c5e193bb3cad2efca03afb2d04c44815e56e2ac9caa2b68d32dd291ce3da9c7debb6646c0de58f4e321df762a3cc55cfc30425221a2f2cb26d10000012800000090471e0b99b2d33a1c6c46137b51404859c121b605ec6ba2d051cb05000ea2af1038fd2f55e333b12143126d8430e576a3102a4cd9b7d8b28bda0fd68c80f9f4907d273e8db07d35367eff9fc93d715a1e1e20c80b76593addecdab503c944603e1ace2d612f482eec9d23e9655552ad9b5d6805379b4ccc26195ad730f4a192cb4f85589fb9f4f0556480a78593348a5000000090b56978aa1d94ff2e2593864144defcc9491429a862274141a65225b58597d15b8eb13ef060cd75c33246df88241964c87eb08f2639f24048a0dd94bbca3b0fb4a6fb82916f2b6fbe1d05504f02fd6b4a67b58c58085864dbb6a26f7359a1e256e7fda12e9f4714dbab35fa1ff05081380bd351516c68b542f7068f2ecad24139b59055599b1cecae902973dd934007a400000128000000901ff8c6da2a57ce3580ed242c3571d9ec27f35ef436367a80bb500e65a97d49e48840fd757984eeee7ed4d7b675e2fa761378d270ceb582a96f4abbb7b90062747ab25a1e6fe505e7c3f2187f38c8996d773676380161053f7eb3f8c97387aa9c6aaaa30b97a546478668ac11ac2447f8845f1139820d679387fda7ae38627b95d2e1bc4a1ec6c50c02b6001ad67dd621000000901a0f38d26a55c352cacce5719aef19a35195aaa7a0660322ecef21d17cdcd4beb2e9fdf909522e68ead4974dbbc61ec727b7c192d649ba9402ef11bab69a4820c4ac8466678684a4561b79c607787a99d0ad4c9bcea79c0f6141417e2b2c08927e61c1412c6116fd383ce2b39f5284887477b1af8a5d4287b6e93cd9f6cc2b20bee631b52586254fca5b4eceadf0fc3b0000012800000090d454eb3e205fb713c0e1f57ac87f7486e283bf5373ab8dd7d6814b51e647c906668e53fdecf34d62c2577741f0326aa2770dbe8ddfa38feb113f296e8154da0bfb8744ffdc53fd9952fc2886e3c9ff4d9bec7fc40bf3b419f7cd8f9fdd94e5a80d858c55d3d8d6a78c4f38aafa2e57d344f3fa33066c02fcef95584f53fd18d77746707d7baf40f3d132e0762ff530da00000090447038e5cb74279855f930e8f7b7766db0fb5d9659e29f100a5edee666e93509b6304c0e244f64f622b85fe2edd61b7fea4129ee9877221583820e8624e32d92ab91f7f912589879ebe09ec84d6188f1afd24505c8d53576e2f4ceb6ec542df20ff8b21341611074c545fbb75dd8e4b9ace680bf1c22d34acd865dbe1ce75aa488b8c373d2601600ffdedd741f163ed500000ce4000001280000009045436ce2e6da0bbfec32c2a11b7de39d7dd557b2c44a87a827375716d3a0bfbf23e4e3027a036a17fe39610b53226ca60bc1a401c108e7a175a79639423d25e87c5bee44843faf52e06dfe9c5889e44696d84e60b00c808fc658cc1ba3a0380981f00a9b7e08844bbd7a30005ba8721b1933fb86fd11c4d51834fffcc1ab349f99bcda485368666b15f1e3668cc181f200000090bd229cf6d6f7d21631b59a23f3dcab0a9e2fd1bc8559a231695b6a5c26615528cf204d00046fb6fb2e67656576cbb6d72d194763c42e03a99b90950193d0d71c906007f18e3e1e3a7dc32ff538e16de9f4fb1ff8469289acb071318c67b9e99de9220af87f0936e67dc7dd73f06476d2c387fffde703d4375bc237c735801c71af114a06051621aead6dd13729d131990000012800000090b1095ccf7c01d057426cbf1f0b64a04461f96293107906aa032cecce60a80895415eaac9d968cf92b9bc23be2e4c28ad899b286877d856e516c45346416a54c7b851d3ee2310822c48ae58b6aa98d77214590692e544beffb4cd7db0b178bd4c73352d26295117d7fb3035e24c5a46375b0351068f8d13314c72c4791ae3ee6327bf1a69223068ebfbf3ee1162b05b3d00000090f9c41e0deba7b51b30183beee0fa118da199df57e7b64f7edabe514b9b054f84a8ac4a874b180c1173b428aede80f9065673f90cb90b50a75d45190411bca2802ec52d39a94964dc713f22e7a84e2b3951dc0147667b61153d4a7f05acc134188a20f930dbecaa1a6bf25d32efa1522651b47f92bd69e4c23a636a55111254efd05580201b50b964a1f32dc9025107a200000128000000903e89093271e6a69b711d07022e4e5e9c150a25620bd439d17551add63a0d8d884e45a3c44128ead9c2d3d8d2c3d4f92a39c8f93cb7578f1163966a8fbfc0e9c525c5d7f722a08d5c7352cb9a75b416603403f181652a7b06f8ca9600e23b4a965cdbd77ea9839d8e7e4d497c383e5e37682638f763ab3cead19ed526720b89d95366d5ff51b3f9c5ae658633f3e2c993000000906a4e1ed85756cd8c10680d71a1836879fac29cebf1d66a65cafca1ddb5b9ccf0c7eff289bdcce8b3dd23a1f904bb453359354d48579cc3fb3fbbf992a3f736154b6c8e1cb2e51c5e2c172cff4d5008dad02365cea9da1d63e00c042a9ba57e413c4a06d155fe9b9f86e262e97463597a82d7ef555baa2341426e0b7158829d50f9e4ecbe06444b6eeec16f96faf4550500000128000000903dc7fdc3162430d001f7977c0763f2ff678dcdf07c2f6a2485deeaf993ec94a2198d9ef36d7cd7f35081345641df105b6bd11a3491769f78c0d903ff1431a9e5dbe4018ab44cf0b9821ecb5ed32eb34ca80dffa898289893d917d9b1fefe265d6a7f8399c9daa180b1febcbc1c558d4746e5704d3405512a4fa1379925e58f33fa44731f43927a950a6c8babbf79a14700000090c596b4a820d0e7a8836b13d5e7f50bc9755865b70d1346e590ca091025a25e0add8c50137f8192b347f75bccce887e3f831d57d21252cab4b69dad4f17e1e0d280d147c23ab8bc57080c4812514ef05f63506743e267f89012fceb91c0688d6f5c434d4c5ff2ae87387e1a186b536e5bf6e98ed3291e5342ce9fa44c90cc5ba90387a4717d6dcb6e059145c1ce8764fa0000012800000090283d76ab2e5af53de1d18916e20f8b86bc6a20176941530f8619cc9cbc9fecc6a93b724f7fcee354a4fd6f195841ab11a75c89d33aeece8eec50e5fd4e546caabd8c04c6b1961a9fc37bc167fc31f3ebc1eb5d6dd77a1d04c733862b604dbfae14f6f9e69fb510ebf18bb64db753b0555b44b49f6fb37e41833c2cc1943c57394e0bd0dc3ce650c720e383a2de6f08fc000000900be60fd6de9a29388caf4f72b62772b80a503fd1449f8de61c72274e30ec13804f576b64345d7aedeff7af7d4b7ca95f03409036f473f35d5007585a2ae57ab94e234c195c07e0b5396f64211659a785e9a3ae4a697f5dca6a0a3ee2800208a107b12f40e5ec28cef8cd611add402aa2b64bf8938bcd1c2e4faebb54fa98cfae7b56b4a7797d5773a3b13808ebf2d3640000012800000090e1a2d111767837b0a5bd9ab4aa040837f01dc81fd1f3b4c94c577bc4334057561d356f081587fe288df0698fd3ca2d1be1fd4533a3d985a514d3b47a8f0e25c6e71b77a0f2a845a9292d016e16dea8c48597750ed24ec27955b749b8c647b1dad52191d4619762d90b395cd161201b5801587c46c623c9e9c85461a0998f13f32f3dabac687c6b37f0452793ec12029300000090339f31d769c14a357e7c98d6f5844e7c8a97ff122b25e0138d144d983c8219f971a67c20aebe049899c7ec09a3c7673ba857a507af1f5df87b53ce538385894b27c2e51f69ca75cb36656b09eaeccbffbea93c8af1dae1a5e8ab4c7c576c1f8521f27aae4d35c5bfb2868134d8dec61c60ebdb108b6f710c7e5ad7cf07026b0ab30df451dafb7d129075b4e2e29e87da0000012800000090446064bffca204655f9bcd32448d5281fc2ff2719cd87b3af607980fca826d2475cbb26176a5581312a871e1f3437f8b56e3e3424c050b0431daa7232ff0628d3c079dd7968229f7f4052650e2535473e346836f424308e4617e69233e04cdaeeb36f1952d6b42c278ae462f8c494d767a736ea0ca5eb4c7213413aa20a50b425b53e56c525fb4e4c9a4446ac4eddbb4000000908c5392f39dde50a945b2a726a59b76e677db424b16a2c17a7ae63c5b8a54db1a3ed78c150a97792402f186fbb8a6e093293ee00ff8b56cba7e772d89c79b87d48dcd17d436d11128b1f84ba989025fe5e25f731ab1fd99185783c13e2b180a344419a78577c541e4fda696c8463bff0b69cc80a23b3278f1149614cac7a019f43e484791680e168ea97e21cd5ec8bcc20000012800000090345b551d49681f065d63636d7c6e71cc73992b3b301f9591c577572b36b22e0fef0ac09ba7d7cfdd0f49a13739ef4314eb78de142191a75976123da2a49640249ccbc2af096e3900b4ca9bd4f09f52600c95f374ba110d6af27c2e1e4bc776af4577cf8cb55af20f521be2a0b9840a9e24c76aecbc0452ba6e07ff5762fd337ad72f9bebd508596940230d3a3648f54900000090ed47ff927b3a0d86080173815ca57523eb6dd5e261e587c375bd495e8bb376c15c14fe2f7086941bcee511a2845b43dce73c03193cb7f4069a18a228f310c87c2ea4c7023a863ebf36b3dd8f205b4cdf8bccfb99add2b6bb9b47057840ae0aad4147bc702c940e977f5be5fc96dbed0f578bd3c8fb05e73a83eca17aab10ff63f7781d4f42164a7ade252a819c7d8c0e0000012800000090a2acbcc47d3d3b5de0fd05c6e9a434a45f8df67d84caf7cc08438cc747261ad6f7d5182e8b4ff771f889ecff987fc81ab3fce9803f1f66ee6042e70026eca90cd8ee628a14cb2819b80062b9583aadcfb87a4b07b4758b162c4836d7e3c65d1406e4023e50cc17103ad1f23a9913eeb466c9626c3753860cbc973345674a43a3a9c3752420a8d4d0968d1dc8c051e0d000000090bec08567dd021f5e9de396dea483bef26c0fce90d2a9810cc0fc5cf979273566c8bcc1a5b7348f8c134c6836db56f06e1ec9bf2f22772212bbd346121021a3e06630212745414ca8ed09a68bcecae7ae5d657a5d0aeeb0787c601fd7ac9164c8dc78b475ab03c24fcd0d069e26386e24a339e387c4aa4aaee8a982836b9fc4f7da4043a4d98acc25a6d734034ffbad800000012800000090529e399a8cd2758e15cd6288caae8a4caf0825dc10342864bb3420a5599c42935ef54ca3c0755585de1d591be4d0a077bef0d0c7bf6a960ce038af375b4a2353866fcffc2e2ed3c9c126a996472b64259ba5bb465be59a2d08d91696dbc24909500d5840aae4a2af179bc4eb0a409b4b8472380e2f327252ed83255cfd2cfdbf6ffd43caea70a475fc62ef5f0f88aa5e00000090d57807dc2c257bb03cc5721539ee708345108fcf0cd13c91142f134800d242e8435773da485f21220c059830b664ed61fb5fbfd7a4815b7e33ab387b4c5a6b2cd5ab5bb7e22608bfdd5b53f95d58276dd44019bf08893e0b527c19c3a537cdf76bf68fee369d43c622e64a8567ef2cb84edd9ca30480d88489f1e31117c08c45e575ed09a075964611b419728712620c00000128000000905c9800416b095f77831bc877a355db545151d4ff58a8d9347dc9a982366aa6da5f9043e7b3320854635dd53d55cd2f283188eb27020f98ab4a2735b23d1f91df763e3272f4fbeb459ea74d93d007ca1dd1162e57b48161b27a2f02b35a7ef5998d4bc0e60b7acb4d31f781a1544d992688d47fa439a6e7e4ce9a8224e4e1c36ef85dd306d8fbdfe2246ef96c98ebd7cf000000904e9a6442af0ec3b24f0e4e472afb25a70dfa8159e23a861e53cd3756e54a9dc02e5a0e2112c1e00c8881ea179393700d41ce4009e93b8c5abcf4244cb2705507bac7d6bf6fbcc6c7e0023b0518765b6bebe0f278ba18c4f1e62102b4affefd24d94713ce05cc156a4a9a0908fe9632892c639af5c86c471a02aa3dae340f061a23b94ca42551f6b15919096dd2d80e87"; function setUp() public virtual { helper = new DecoderHelper(); diff --git a/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts b/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts index 5b93133e4060..1bc1200f2ffb 100644 --- a/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts +++ b/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts @@ -109,6 +109,17 @@ export class Oracle { return witness.toFieldArray().map(toACVMField); } + async getPublicDataTreeWitness([blockNumber]: ACVMField[], [leafSlot]: ACVMField[]): Promise { + const parsedBlockNumber = frToNumber(fromACVMField(blockNumber)); + const parsedLeafSlot = fromACVMField(leafSlot); + + const witness = await this.typedOracle.getPublicDataTreeWitness(parsedBlockNumber, parsedLeafSlot); + if (!witness) { + throw new Error(`Public data witness not found for slot ${parsedLeafSlot} at block ${parsedBlockNumber}.`); + } + return witness.toFieldArray().map(toACVMField); + } + async getBlockHeader([blockNumber]: ACVMField[]): Promise { const parsedBlockNumber = frToNumber(fromACVMField(blockNumber)); diff --git a/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts b/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts index 556da1bebf8d..d1d4419b1f25 100644 --- a/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts +++ b/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts @@ -8,6 +8,7 @@ import { MerkleTreeId, Note, NullifierMembershipWitness, + PublicDataWitness, PublicKey, UnencryptedL2Log, } from '@aztec/types'; @@ -95,6 +96,10 @@ export abstract class TypedOracle { throw new Error('Not available.'); } + getPublicDataTreeWitness(_blockNumber: number, _leafSlot: Fr): Promise { + throw new Error('Not available.'); + } + getLowNullifierMembershipWitness( _blockNumber: number, _nullifier: Fr, diff --git a/yarn-project/acir-simulator/src/client/db_oracle.ts b/yarn-project/acir-simulator/src/client/db_oracle.ts index 5d866dac224b..17b9171ff7ff 100644 --- a/yarn-project/acir-simulator/src/client/db_oracle.ts +++ b/yarn-project/acir-simulator/src/client/db_oracle.ts @@ -3,7 +3,7 @@ import { FunctionArtifact, FunctionDebugMetadata, FunctionSelector } from '@azte import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; -import { L2Block, MerkleTreeId, NullifierMembershipWitness } from '@aztec/types'; +import { L2Block, MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/types'; import { NoteData } from '../acvm/index.js'; import { CommitmentsDB } from '../public/index.js'; @@ -153,6 +153,13 @@ export interface DBOracle extends CommitmentsDB { */ getLowNullifierMembershipWitness(blockNumber: number, nullifier: Fr): Promise; + /** + * Returns a witness for a given slot of the public data tree at a given block. + * @param blockNumber - The block number at which to get the witness. + * @param leafSlot - The slot of the public data in the public data tree. + */ + getPublicDataTreeWitness(blockNumber: number, leafSlot: Fr): Promise; + /** * Fetch a block corresponding to the given block number. * @param blockNumber - The block number of a block to fetch. diff --git a/yarn-project/acir-simulator/src/client/view_data_oracle.ts b/yarn-project/acir-simulator/src/client/view_data_oracle.ts index 1edcfe3998b1..8297c63c5a17 100644 --- a/yarn-project/acir-simulator/src/client/view_data_oracle.ts +++ b/yarn-project/acir-simulator/src/client/view_data_oracle.ts @@ -10,6 +10,7 @@ import { INITIAL_L2_BLOCK_NUM, MerkleTreeId, NullifierMembershipWitness, + PublicDataWitness, } from '@aztec/types'; import { NoteData, TypedOracle } from '../acvm/index.js'; @@ -98,6 +99,16 @@ export class ViewDataOracle extends TypedOracle { return await this.db.getLowNullifierMembershipWitness(blockNumber, nullifier); } + /** + * Returns a public data tree witness for a given leaf slot at a given block. + * @param blockNumber - The block number at which to get the index. + * @param leafSlot - The slot of the public data tree to get the witness for. + * @returns - The witness + */ + public async getPublicDataTreeWitness(blockNumber: number, leafSlot: Fr): Promise { + return await this.db.getPublicDataTreeWitness(blockNumber, leafSlot); + } + /** * Fetches a block header of a given block. * @param blockNumber - The number of a block of which to get the block header. @@ -115,7 +126,7 @@ export class ViewDataOracle extends TypedOracle { block.endL1ToL2MessagesTreeSnapshot.root, block.endArchiveSnapshot.root, new Fr(0), // TODO(#3441) privateKernelVkTreeRoot is not present in L2Block and it's not yet populated in noir - block.endPublicDataTreeRoot, + block.endPublicDataTreeSnapshot.root, computeGlobalsHash(block.globalVariables), ); } @@ -257,9 +268,6 @@ export class ViewDataOracle extends TypedOracle { for (let i = 0n; i < numberOfElements; i++) { const storageSlot = new Fr(startStorageSlot.value + i); const value = await this.aztecNode.getPublicStorageAt(this.contractAddress, storageSlot); - if (value === undefined) { - throw new Error(`Oracle storage read undefined: slot=${storageSlot.toString()}`); - } this.log(`Oracle storage read: slot=${storageSlot.toString()} value=${value}`); values.push(value); diff --git a/yarn-project/acir-simulator/src/public/execution.ts b/yarn-project/acir-simulator/src/public/execution.ts index 9d2b20470005..287395acebc4 100644 --- a/yarn-project/acir-simulator/src/public/execution.ts +++ b/yarn-project/acir-simulator/src/public/execution.ts @@ -8,7 +8,7 @@ import { PublicDataRead, PublicDataUpdateRequest, } from '@aztec/circuits.js'; -import { computePublicDataTreeIndex, computePublicDataTreeValue } from '@aztec/circuits.js/abis'; +import { computePublicDataTreeLeafSlot, computePublicDataTreeValue } from '@aztec/circuits.js/abis'; import { FunctionL2Logs } from '@aztec/types'; /** @@ -111,7 +111,7 @@ export function collectPublicDataUpdateRequests(execResult: PublicExecutionResul */ function contractStorageReadToPublicDataRead(read: ContractStorageRead, contractAddress: AztecAddress): PublicDataRead { return new PublicDataRead( - computePublicDataTreeIndex(contractAddress, read.storageSlot), + computePublicDataTreeLeafSlot(contractAddress, read.storageSlot), computePublicDataTreeValue(read.currentValue), read.sideEffectCounter!, ); @@ -128,7 +128,7 @@ function contractStorageUpdateRequestToPublicDataUpdateRequest( contractAddress: AztecAddress, ): PublicDataUpdateRequest { return new PublicDataUpdateRequest( - computePublicDataTreeIndex(contractAddress, update.storageSlot), + computePublicDataTreeLeafSlot(contractAddress, update.storageSlot), computePublicDataTreeValue(update.oldValue), computePublicDataTreeValue(update.newValue), update.sideEffectCounter!, diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 7edaa26cbd9a..5a1b9884606e 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -10,8 +10,9 @@ import { NULLIFIER_TREE_HEIGHT, NullifierLeafPreimage, PUBLIC_DATA_TREE_HEIGHT, + PublicDataTreeLeafPreimage, } from '@aztec/circuits.js'; -import { computeGlobalsHash, computePublicDataTreeIndex } from '@aztec/circuits.js/abis'; +import { computeGlobalsHash, computePublicDataTreeLeafSlot } from '@aztec/circuits.js/abis'; import { L1ContractAddresses, createEthereumChain } from '@aztec/ethereum'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { createDebugLogger } from '@aztec/foundation/log'; @@ -40,6 +41,7 @@ import { LogType, MerkleTreeId, NullifierMembershipWitness, + PublicDataWitness, SequencerConfig, SiblingPath, Tx, @@ -481,6 +483,24 @@ export class AztecNodeService implements AztecNode { return new NullifierMembershipWitness(BigInt(index), preimageData as NullifierLeafPreimage, siblingPath); } + async getPublicDataTreeWitness(blockNumber: number | 'latest', leafSlot: Fr): Promise { + const committedDb = await this.#getWorldState(blockNumber); + const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt()); + if (!lowLeafResult) { + return undefined; + } else { + const preimage = (await committedDb.getLeafPreimage( + MerkleTreeId.PUBLIC_DATA_TREE, + lowLeafResult.index, + )) as PublicDataTreeLeafPreimage; + const path = await committedDb.getSiblingPath( + MerkleTreeId.PUBLIC_DATA_TREE, + lowLeafResult.index, + ); + return new PublicDataWitness(lowLeafResult.index, preimage, path); + } + } + /** * Gets the storage value at the given contract storage slot. * @@ -489,13 +509,21 @@ export class AztecNodeService implements AztecNode { * * @param contract - Address of the contract to query. * @param slot - Slot to query. - * @returns Storage value at the given contract slot (or undefined if not found). + * @returns Storage value at the given contract slot. */ - public async getPublicStorageAt(contract: AztecAddress, slot: Fr): Promise { + public async getPublicStorageAt(contract: AztecAddress, slot: Fr): Promise { const committedDb = await this.#getWorldState('latest'); - const leafIndex = computePublicDataTreeIndex(contract, slot); - const value = await committedDb.getLeafValue(MerkleTreeId.PUBLIC_DATA_TREE, leafIndex.value); - return value ? Fr.fromBuffer(value) : undefined; + const leafSlot = computePublicDataTreeLeafSlot(contract, slot); + + const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt()); + if (!lowLeafResult || !lowLeafResult.alreadyPresent) { + return Fr.ZERO; + } + const preimage = (await committedDb.getLeafPreimage( + MerkleTreeId.PUBLIC_DATA_TREE, + lowLeafResult.index, + )) as PublicDataTreeLeafPreimage; + return preimage.value; } /** diff --git a/yarn-project/aztec-nr/aztec/src/history/public_value_inclusion.nr b/yarn-project/aztec-nr/aztec/src/history/public_value_inclusion.nr index dd03fed9d3c0..45f2c5450f66 100644 --- a/yarn-project/aztec-nr/aztec/src/history/public_value_inclusion.nr +++ b/yarn-project/aztec-nr/aztec/src/history/public_value_inclusion.nr @@ -6,7 +6,13 @@ use dep::std::merkle::compute_merkle_root; use crate::{ context::PrivateContext, - oracle::get_sibling_path::get_public_data_sibling_path, + oracle::get_public_data_witness::{ + get_public_data_witness, + PublicDataWitness, + }, + utils::{ + full_field_less_than, + }, }; pub fn prove_public_value_inclusion( @@ -18,24 +24,30 @@ pub fn prove_public_value_inclusion( // 1) Get block header from oracle and ensure that the block hash is included in the archive. let block_header = context.get_block_header(block_number); - // 2) Compute the public value leaf index. - // We have to compute the leaf index here because unlike in the case of note commitments, public values are - // not siloed with contract address so an oracle could cheat and give us a membership witness for arbitrary - // value in the public data tree. - let value_leaf_index = pedersen_hash( + // 2) Compute the leaf slot by siloing the storage slot with our own address + let public_value_leaf_slot = pedersen_hash( [context.this_address().to_field(), storage_slot], GENERATOR_INDEX__PUBLIC_LEAF_INDEX ); - // 3) Get the sibling path of the value leaf index in the public data tree at block `block_number`. - let path = get_public_data_sibling_path(block_number, value_leaf_index); + // 3) Get the membership witness of the slot + let witness = get_public_data_witness(block_number, public_value_leaf_slot); - // 4) Prove that the value provided on input is in the public data tree at the given storage slot. + // 4) Check that the witness matches the corresponding public_value + let preimage = witness.leaf_preimage; + if preimage.slot == public_value_leaf_slot { + assert_eq(preimage.value, value, "Public value does not match value in witness"); + } else { + assert_eq(value, 0, "Got non-zero public value for non-existing slot"); + assert(full_field_less_than(preimage.slot, public_value_leaf_slot), "Invalid witness range"); + assert(full_field_less_than(public_value_leaf_slot, preimage.next_slot), "Invalid witness range"); + } + + // 5) Prove that the leaf we validated is in the public data tree assert( - block_header.public_data_tree_root == compute_merkle_root(value, value_leaf_index, path), - "Proving public value inclusion failed" + block_header.public_data_tree_root + == compute_merkle_root(preimage.hash(), witness.index, witness.path), "Proving public value inclusion failed" ); - // --> Now we have traversed the trees all the way up to archive root and that way verified that a specific // `value` was really set in a given contract storage slot at block `block_number` in public data tree. -} \ No newline at end of file +} diff --git a/yarn-project/aztec-nr/aztec/src/oracle.nr b/yarn-project/aztec-nr/aztec/src/oracle.nr index 6a7aac259db1..edd47519d2b3 100644 --- a/yarn-project/aztec-nr/aztec/src/oracle.nr +++ b/yarn-project/aztec-nr/aztec/src/oracle.nr @@ -8,6 +8,7 @@ mod context; mod debug_log; mod get_l1_to_l2_message; mod get_nullifier_membership_witness; +mod get_public_data_witness; mod get_membership_witness; mod get_public_key; mod get_secret_key; diff --git a/yarn-project/aztec-nr/aztec/src/oracle/get_public_data_witness.nr b/yarn-project/aztec-nr/aztec/src/oracle/get_public_data_witness.nr new file mode 100644 index 000000000000..555a80785801 --- /dev/null +++ b/yarn-project/aztec-nr/aztec/src/oracle/get_public_data_witness.nr @@ -0,0 +1,47 @@ +use dep::protocol_types::constants::PUBLIC_DATA_TREE_HEIGHT; +use dep::protocol_types::hash::pedersen_hash; +use crate::utils::arr_copy_slice; + +global LEAF_PREIMAGE_LENGTH: Field = 4; +// TODO: move this to constants_gen.nr so that it gets computed as INDEX_LENGTH + LEAF_DATA_LENGTH + PUBLIC_DATA_TREE_HEIGHT +global PUBLIC_DATA_WITNESS: Field = 45; + +// TODO(#3470) replace with /mnt/user-data/jan/aztec-packages/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/abis/public_data_tree_leaf.nr +struct PublicDataTreeLeafPreimage { + slot : Field, + value: Field, + next_index : u32, + next_slot :Field, +} + +impl PublicDataTreeLeafPreimage { + fn serialize(self) -> [Field; LEAF_PREIMAGE_LENGTH] { + [self.slot, self.value, self.next_index as Field, self.next_slot] + } + + fn hash(self) -> Field { + // Performs the same hashing as StandardIndexedTree::encodeLeaf(...) + pedersen_hash(self.serialize(), 0) + } +} + +struct PublicDataWitness { + index: Field, + leaf_preimage: PublicDataTreeLeafPreimage, + path: [Field; PUBLIC_DATA_TREE_HEIGHT], +} + +#[oracle(getPublicDataTreeWitness)] +fn get_public_data_witness_oracle(_block_number: u32, _leaf_slot: Field) -> [Field; PUBLIC_DATA_WITNESS] {} + +unconstrained pub fn get_public_data_witness( + block_number: u32, + leaf_slot: Field +) -> PublicDataWitness { + let fields = get_public_data_witness_oracle(block_number, leaf_slot); + PublicDataWitness { + index: fields[0], + leaf_preimage: PublicDataTreeLeafPreimage { slot: fields[1], value: fields[2], next_index: fields[3] as u32, next_slot: fields[4] }, + path: arr_copy_slice(fields, [0; PUBLIC_DATA_TREE_HEIGHT], 1 + LEAF_PREIMAGE_LENGTH) + } +} diff --git a/yarn-project/aztec-nr/aztec/src/oracle/get_sibling_path.nr b/yarn-project/aztec-nr/aztec/src/oracle/get_sibling_path.nr index b0daa26cee4e..b72dfbecb8bd 100644 --- a/yarn-project/aztec-nr/aztec/src/oracle/get_sibling_path.nr +++ b/yarn-project/aztec-nr/aztec/src/oracle/get_sibling_path.nr @@ -3,15 +3,11 @@ use dep::protocol_types::constants::PUBLIC_DATA_TREE_HEIGHT; #[oracle(getSiblingPath)] fn get_sibling_path_oracle(_block_number: u32, _tree_id: Field, _leaf_index: Field) -> [Field; N] {} -unconstrained pub fn get_sibling_path(block_number: u32, tree_id: Field, leaf_index: Field) -> [Field; N] { +unconstrained pub fn get_sibling_path( + block_number: u32, + tree_id: Field, + leaf_index: Field +) -> [Field; N] { let value: [Field; N] = get_sibling_path_oracle(block_number, tree_id, leaf_index); value } - -unconstrained pub fn get_public_data_sibling_path(block_number: u32, leaf_index: Field) -> [Field; PUBLIC_DATA_TREE_HEIGHT] { - let public_data_tree_id = 3; // TODO(#3443) - get_sibling_path(block_number, public_data_tree_id, leaf_index) -} - -// We don't implement specific function for other trees than public data tree because for the rest it makes sense -// to use get membership witness function instead. \ No newline at end of file diff --git a/yarn-project/aztec.js/src/utils/cheat_codes.ts b/yarn-project/aztec.js/src/utils/cheat_codes.ts index 4b378c3cd9fd..e8ea11bda59e 100644 --- a/yarn-project/aztec.js/src/utils/cheat_codes.ts +++ b/yarn-project/aztec.js/src/utils/cheat_codes.ts @@ -280,9 +280,6 @@ export class AztecCheatCodes { */ public async loadPublic(who: AztecAddress, slot: Fr | bigint): Promise { const storageValue = await this.pxe.getPublicStorageAt(who, new Fr(slot)); - if (storageValue === undefined) { - throw new Error(`Storage slot ${slot} not found`); - } return storageValue; } diff --git a/yarn-project/circuits.js/src/abis/__snapshots__/abis.test.ts.snap b/yarn-project/circuits.js/src/abis/__snapshots__/abis.test.ts.snap index 71d1cdce74ab..b75a96b68961 100644 --- a/yarn-project/circuits.js/src/abis/__snapshots__/abis.test.ts.snap +++ b/yarn-project/circuits.js/src/abis/__snapshots__/abis.test.ts.snap @@ -653,7 +653,7 @@ Fr { } `; -exports[`abis computes public data tree index 1`] = ` +exports[`abis computes public data tree leaf slot 1`] = ` Fr { "asBigInt": 9076808949980998475110411569159266589807853958487763065147292518713994520820n, "asBuffer": { diff --git a/yarn-project/circuits.js/src/abis/abis.test.ts b/yarn-project/circuits.js/src/abis/abis.test.ts index 5aac33e2968b..d49af99b41b2 100644 --- a/yarn-project/circuits.js/src/abis/abis.test.ts +++ b/yarn-project/circuits.js/src/abis/abis.test.ts @@ -30,7 +30,7 @@ import { computeGlobalsHash, computePrivateCallStackItemHash, computePublicCallStackItemHash, - computePublicDataTreeIndex, + computePublicDataTreeLeafSlot, computePublicDataTreeValue, computeSecretMessageHash, computeTxHash, @@ -165,10 +165,10 @@ describe('abis', () => { expect(res).toMatchSnapshot(); }); - it('computes public data tree index', () => { + it('computes public data tree leaf slot', () => { const contractAddress = makeAztecAddress(); const value = new Fr(3n); - const res = computePublicDataTreeIndex(contractAddress, value); + const res = computePublicDataTreeLeafSlot(contractAddress, value); expect(res).toMatchSnapshot(); }); diff --git a/yarn-project/circuits.js/src/abis/abis.ts b/yarn-project/circuits.js/src/abis/abis.ts index 6d268d655185..0bfa49b351b3 100644 --- a/yarn-project/circuits.js/src/abis/abis.ts +++ b/yarn-project/circuits.js/src/abis/abis.ts @@ -366,7 +366,7 @@ export function computePublicDataTreeValue(value: Fr): Fr { * @returns Public data tree index computed from contract address and storage slot. */ -export function computePublicDataTreeIndex(contractAddress: AztecAddress, storageSlot: Fr): Fr { +export function computePublicDataTreeLeafSlot(contractAddress: AztecAddress, storageSlot: Fr): Fr { return Fr.fromBuffer( pedersenHash([contractAddress.toBuffer(), storageSlot.toBuffer()], GeneratorIndex.PUBLIC_LEAF_INDEX), ); diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index f0cfac323a96..fbe8599a71c3 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -33,7 +33,7 @@ export const VK_TREE_HEIGHT = 3; export const FUNCTION_TREE_HEIGHT = 5; export const CONTRACT_TREE_HEIGHT = 16; export const NOTE_HASH_TREE_HEIGHT = 32; -export const PUBLIC_DATA_TREE_HEIGHT = 254; +export const PUBLIC_DATA_TREE_HEIGHT = 40; export const NULLIFIER_TREE_HEIGHT = 20; export const L1_TO_L2_MSG_TREE_HEIGHT = 16; export const ROLLUP_VK_TREE_HEIGHT = 8; @@ -42,8 +42,10 @@ export const CONTRACT_SUBTREE_SIBLING_PATH_LENGTH = 15; export const NOTE_HASH_SUBTREE_HEIGHT = 7; export const NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH = 25; export const NULLIFIER_SUBTREE_HEIGHT = 7; +export const PUBLIC_DATA_SUBTREE_HEIGHT = 4; export const ARCHIVE_HEIGHT = 16; export const NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH = 13; +export const PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH = 36; export const L1_TO_L2_MSG_SUBTREE_HEIGHT = 4; export const L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH = 12; export const FUNCTION_SELECTOR_NUM_BYTES = 4; diff --git a/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts b/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts index f77a3d910c44..3dff253c15cd 100644 --- a/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts +++ b/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts @@ -180,7 +180,7 @@ export class PublicDataRead { /** * Index of the leaf in the public data tree. */ - public readonly leafIndex: Fr, + public readonly leafSlot: Fr, /** * Returned value from the public data tree. */ @@ -205,7 +205,7 @@ export class PublicDataRead { } toBuffer() { - return serializeToBuffer(this.leafIndex, this.value); + return serializeToBuffer(this.leafSlot, this.value); } static fromBuffer(buffer: Buffer | BufferReader) { @@ -218,7 +218,7 @@ export class PublicDataRead { } toFriendlyJSON() { - return `Leaf=${this.leafIndex.toFriendlyJSON()}: ${this.value.toFriendlyJSON()}`; + return `Leaf=${this.leafSlot.toFriendlyJSON()}: ${this.value.toFriendlyJSON()}`; } } @@ -230,7 +230,7 @@ export class PublicDataUpdateRequest { /** * Index of the leaf in the public data tree which is to be updated. */ - public readonly leafIndex: Fr, + public readonly leafSlot: Fr, /** * Old value of the leaf. */ @@ -263,7 +263,7 @@ export class PublicDataUpdateRequest { } toBuffer() { - return serializeToBuffer(this.leafIndex, this.oldValue, this.newValue); + return serializeToBuffer(this.leafSlot, this.oldValue, this.newValue); } static fromBuffer(buffer: Buffer | BufferReader) { @@ -276,7 +276,7 @@ export class PublicDataUpdateRequest { } toFriendlyJSON() { - return `Leaf=${this.leafIndex.toFriendlyJSON()}: ${this.oldValue.toFriendlyJSON()} => ${this.newValue.toFriendlyJSON()}`; + return `Leaf=${this.leafSlot.toFriendlyJSON()}: ${this.oldValue.toFriendlyJSON()} => ${this.newValue.toFriendlyJSON()}`; } } diff --git a/yarn-project/circuits.js/src/structs/rollup/base_or_merge_rollup_public_inputs.ts b/yarn-project/circuits.js/src/structs/rollup/base_or_merge_rollup_public_inputs.ts index c58f778a9948..a5e94ac50232 100644 --- a/yarn-project/circuits.js/src/structs/rollup/base_or_merge_rollup_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/rollup/base_or_merge_rollup_public_inputs.ts @@ -60,13 +60,13 @@ export class BaseOrMergeRollupPublicInputs { public endContractTreeSnapshot: AppendOnlyTreeSnapshot, /** - * Root of the public data tree at the start of the rollup circuit. + * Snapshot of the public data tree at the start of the rollup circuit. */ - public startPublicDataTreeRoot: Fr, + public startPublicDataTreeSnapshot: AppendOnlyTreeSnapshot, /** - * Root of the public data tree at the end of the rollup circuit. + * Snapshot of the public data tree at the end of the rollup circuit. */ - public endPublicDataTreeRoot: Fr, + public endPublicDataTreeSnapshot: AppendOnlyTreeSnapshot, /** * SHA256 hashes of calldata. Used to make public inputs constant-sized (to then be unpacked on-chain). @@ -94,8 +94,8 @@ export class BaseOrMergeRollupPublicInputs { reader.readObject(AppendOnlyTreeSnapshot), reader.readObject(AppendOnlyTreeSnapshot), reader.readObject(AppendOnlyTreeSnapshot), - Fr.fromBuffer(reader), - Fr.fromBuffer(reader), + reader.readObject(AppendOnlyTreeSnapshot), + reader.readObject(AppendOnlyTreeSnapshot), reader.readArray(NUM_FIELDS_PER_SHA256, Fr) as [Fr, Fr], ); } @@ -120,8 +120,8 @@ export class BaseOrMergeRollupPublicInputs { this.startContractTreeSnapshot, this.endContractTreeSnapshot, - this.startPublicDataTreeRoot, - this.endPublicDataTreeRoot, + this.startPublicDataTreeSnapshot, + this.endPublicDataTreeSnapshot, this.calldataHash, ); diff --git a/yarn-project/circuits.js/src/structs/rollup/base_rollup.ts b/yarn-project/circuits.js/src/structs/rollup/base_rollup.ts index c6db387a093f..38d827de360b 100644 --- a/yarn-project/circuits.js/src/structs/rollup/base_rollup.ts +++ b/yarn-project/circuits.js/src/structs/rollup/base_rollup.ts @@ -1,18 +1,17 @@ -import { toBigIntBE, toBufferBE } from '@aztec/foundation/bigint-buffer'; import { Fr } from '@aztec/foundation/fields'; import { BufferReader, Tuple } from '@aztec/foundation/serialize'; -import { IndexedTreeLeaf, IndexedTreeLeafPreimage } from '@aztec/foundation/trees'; import { ARCHIVE_HEIGHT, CONTRACT_SUBTREE_SIBLING_PATH_LENGTH, KERNELS_PER_BASE_ROLLUP, MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP, - MAX_PUBLIC_DATA_READS_PER_BASE_ROLLUP, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_BASE_ROLLUP, + MAX_PUBLIC_DATA_READS_PER_TX, + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_TREE_HEIGHT, + PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, PUBLIC_DATA_TREE_HEIGHT, } from '../../constants.gen.js'; import { FieldsOf } from '../../utils/jsUtils.js'; @@ -22,110 +21,10 @@ import { PreviousKernelData } from '../kernel/previous_kernel_data.js'; import { MembershipWitness } from '../membership_witness.js'; import { UInt32 } from '../shared.js'; import { AppendOnlyTreeSnapshot } from './append_only_tree_snapshot.js'; +import { NullifierLeaf, NullifierLeafPreimage } from './nullifier_leaf/index.js'; +import { PublicDataTreeLeaf, PublicDataTreeLeafPreimage } from './public_data_leaf/index.js'; -/** - * Class containing the data of a preimage of a single leaf in the nullifier tree. - * Note: It's called preimage because this data gets hashed before being inserted as a node into the `IndexedTree`. - */ -export class NullifierLeafPreimage implements IndexedTreeLeafPreimage { - constructor( - /** - * Leaf value inside the indexed tree's linked list. - */ - public nullifier: Fr, - /** - * Next value inside the indexed tree's linked list. - */ - public nextNullifier: Fr, - /** - * Index of the next leaf in the indexed tree's linked list. - */ - public nextIndex: bigint, - ) {} - - getKey(): bigint { - return this.nullifier.toBigInt(); - } - - getNextKey(): bigint { - return this.nextNullifier.toBigInt(); - } - - getNextIndex(): bigint { - return this.nextIndex; - } - - asLeaf(): NullifierLeaf { - return new NullifierLeaf(this.nullifier); - } - - toBuffer(): Buffer { - return Buffer.concat(this.toHashInputs()); - } - - toHashInputs(): Buffer[] { - return [ - Buffer.from(this.nullifier.toBuffer()), - Buffer.from(toBufferBE(this.nextIndex, 32)), - Buffer.from(this.nextNullifier.toBuffer()), - ]; - } - - clone(): NullifierLeafPreimage { - return new NullifierLeafPreimage(this.nullifier, this.nextNullifier, this.nextIndex); - } - - static empty(): NullifierLeafPreimage { - return new NullifierLeafPreimage(Fr.ZERO, Fr.ZERO, 0n); - } - - static fromBuffer(buf: Buffer): NullifierLeafPreimage { - const nullifier = Fr.fromBuffer(buf.subarray(0, 32)); - const nextIndex = toBigIntBE(buf.subarray(32, 64)); - const nextNullifier = Fr.fromBuffer(buf.subarray(64, 96)); - return new NullifierLeafPreimage(nullifier, nextNullifier, nextIndex); - } - - static fromLeaf(leaf: NullifierLeaf, nextKey: bigint, nextIndex: bigint): NullifierLeafPreimage { - return new NullifierLeafPreimage(leaf.nullifier, new Fr(nextKey), nextIndex); - } - - static clone(preimage: NullifierLeafPreimage): NullifierLeafPreimage { - return new NullifierLeafPreimage(preimage.nullifier, preimage.nextNullifier, preimage.nextIndex); - } -} - -/** - * A nullifier to be inserted in the nullifier tree. - */ -export class NullifierLeaf implements IndexedTreeLeaf { - constructor( - /** - * Nullifier value. - */ - public nullifier: Fr, - ) {} - - getKey(): bigint { - return this.nullifier.toBigInt(); - } - - toBuffer(): Buffer { - return this.nullifier.toBuffer(); - } - - isEmpty(): boolean { - return this.nullifier.isZero(); - } - - static buildDummy(key: bigint): NullifierLeaf { - return new NullifierLeaf(new Fr(key)); - } - - static fromBuffer(buf: Buffer): NullifierLeaf { - return new NullifierLeaf(Fr.fromBuffer(buf)); - } -} +export { NullifierLeaf, NullifierLeafPreimage, PublicDataTreeLeaf, PublicDataTreeLeafPreimage }; /** * Data which is forwarded through the base rollup circuits unchanged. @@ -213,9 +112,9 @@ export class BaseRollupInputs { */ public startContractTreeSnapshot: AppendOnlyTreeSnapshot, /** - * Root of the public data tree at the start of the base rollup circuit. + * Snapshot of the public data tree at the start of the base rollup circuit. */ - public startPublicDataTreeRoot: Fr, + public startPublicDataTreeSnapshot: AppendOnlyTreeSnapshot, /** * Snapshot of the blocks tree at the start of the base rollup circuit. */ @@ -242,6 +141,7 @@ export class BaseRollupInputs { MembershipWitness, typeof MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP >, + /** * Sibling path "pointing to" where the new commitments subtree should be inserted into the note hash tree. */ @@ -255,21 +155,60 @@ export class BaseRollupInputs { */ public newContractsSubtreeSiblingPath: Tuple, /** - * Sibling paths of leaves which are to be affected by the public data update requests. - * Each item in the array is the sibling path that corresponds to an update request. + * The public data writes to be inserted in the tree, sorted high slot to low slot. + */ + public sortedPublicDataWrites: Tuple< + Tuple, + typeof KERNELS_PER_BASE_ROLLUP + >, + /** + * The indexes of the sorted public data writes to the original ones. + */ + public sortedPublicDataWritesIndexes: Tuple< + Tuple, + typeof KERNELS_PER_BASE_ROLLUP + >, + /** + * The public data writes which need to be updated to perform the batch insertion of the new public data writes. + * See `StandardIndexedTree.batchInsert` function for more details. + */ + public lowPublicDataWritesPreimages: Tuple< + Tuple, + typeof KERNELS_PER_BASE_ROLLUP + >, + /** + * Membership witnesses for the nullifiers which need to be updated to perform the batch insertion of the new + * nullifiers. + */ + public lowPublicDataWritesMembershipWitnesses: Tuple< + Tuple, typeof MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX>, + typeof KERNELS_PER_BASE_ROLLUP + >, + + /** + * Sibling path "pointing to" where the new public data subtree should be inserted into the public data tree. + */ + public publicDataWritesSubtreeSiblingPaths: Tuple< + Tuple, + typeof KERNELS_PER_BASE_ROLLUP + >, + + /** + * Preimages of leaves which are to be read by the public data reads. */ - public newPublicDataUpdateRequestsSiblingPaths: Tuple< - Tuple, - typeof MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_BASE_ROLLUP + public publicDataReadsPreimages: Tuple< + Tuple, + typeof KERNELS_PER_BASE_ROLLUP >, /** * Sibling paths of leaves which are to be read by the public data reads. * Each item in the array is the sibling path that corresponds to a read request. */ - public newPublicDataReadsSiblingPaths: Tuple< - Tuple, - typeof MAX_PUBLIC_DATA_READS_PER_BASE_ROLLUP + public publicDataReadsMembershipWitnesses: Tuple< + Tuple, typeof MAX_PUBLIC_DATA_READS_PER_TX>, + typeof KERNELS_PER_BASE_ROLLUP >, + /** * Membership witnesses of blocks referred by each of the 2 kernels. */ @@ -293,7 +232,7 @@ export class BaseRollupInputs { fields.startNoteHashTreeSnapshot, fields.startNullifierTreeSnapshot, fields.startContractTreeSnapshot, - fields.startPublicDataTreeRoot, + fields.startPublicDataTreeSnapshot, fields.archiveSnapshot, fields.sortedNewNullifiers, fields.sortednewNullifiersIndexes, @@ -302,8 +241,13 @@ export class BaseRollupInputs { fields.newCommitmentsSubtreeSiblingPath, fields.newNullifiersSubtreeSiblingPath, fields.newContractsSubtreeSiblingPath, - fields.newPublicDataUpdateRequestsSiblingPaths, - fields.newPublicDataReadsSiblingPaths, + fields.sortedPublicDataWrites, + fields.sortedPublicDataWritesIndexes, + fields.lowPublicDataWritesPreimages, + fields.lowPublicDataWritesMembershipWitnesses, + fields.publicDataWritesSubtreeSiblingPaths, + fields.publicDataReadsPreimages, + fields.publicDataReadsMembershipWitnesses, fields.archiveRootMembershipWitnesses, fields.constants, ] as const; diff --git a/yarn-project/circuits.js/src/structs/rollup/nullifier_leaf/index.ts b/yarn-project/circuits.js/src/structs/rollup/nullifier_leaf/index.ts new file mode 100644 index 000000000000..b6bdba72e519 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/rollup/nullifier_leaf/index.ts @@ -0,0 +1,111 @@ +import { toBigIntBE, toBufferBE } from '@aztec/foundation/bigint-buffer'; +import { Fr } from '@aztec/foundation/fields'; +import { IndexedTreeLeaf, IndexedTreeLeafPreimage } from '@aztec/foundation/trees'; + +/** + * Class containing the data of a preimage of a single leaf in the nullifier tree. + * Note: It's called preimage because this data gets hashed before being inserted as a node into the `IndexedTree`. + */ +export class NullifierLeafPreimage implements IndexedTreeLeafPreimage { + constructor( + /** + * Leaf value inside the indexed tree's linked list. + */ + public nullifier: Fr, + /** + * Next value inside the indexed tree's linked list. + */ + public nextNullifier: Fr, + /** + * Index of the next leaf in the indexed tree's linked list. + */ + public nextIndex: bigint, + ) {} + + getKey(): bigint { + return this.nullifier.toBigInt(); + } + + getNextKey(): bigint { + return this.nextNullifier.toBigInt(); + } + + getNextIndex(): bigint { + return this.nextIndex; + } + + asLeaf(): NullifierLeaf { + return new NullifierLeaf(this.nullifier); + } + + toBuffer(): Buffer { + return Buffer.concat(this.toHashInputs()); + } + + toHashInputs(): Buffer[] { + return [ + Buffer.from(this.nullifier.toBuffer()), + Buffer.from(toBufferBE(this.nextIndex, 32)), + Buffer.from(this.nextNullifier.toBuffer()), + ]; + } + + clone(): NullifierLeafPreimage { + return new NullifierLeafPreimage(this.nullifier, this.nextNullifier, this.nextIndex); + } + + static empty(): NullifierLeafPreimage { + return new NullifierLeafPreimage(Fr.ZERO, Fr.ZERO, 0n); + } + + static fromBuffer(buf: Buffer): NullifierLeafPreimage { + const nullifier = Fr.fromBuffer(buf.subarray(0, 32)); + const nextIndex = toBigIntBE(buf.subarray(32, 64)); + const nextNullifier = Fr.fromBuffer(buf.subarray(64, 96)); + return new NullifierLeafPreimage(nullifier, nextNullifier, nextIndex); + } + + static fromLeaf(leaf: NullifierLeaf, nextKey: bigint, nextIndex: bigint): NullifierLeafPreimage { + return new NullifierLeafPreimage(leaf.nullifier, new Fr(nextKey), nextIndex); + } + + static clone(preimage: NullifierLeafPreimage): NullifierLeafPreimage { + return new NullifierLeafPreimage(preimage.nullifier, preimage.nextNullifier, preimage.nextIndex); + } +} + +/** + * A nullifier to be inserted in the nullifier tree. + */ +export class NullifierLeaf implements IndexedTreeLeaf { + constructor( + /** + * Nullifier value. + */ + public nullifier: Fr, + ) {} + + getKey(): bigint { + return this.nullifier.toBigInt(); + } + + toBuffer(): Buffer { + return this.nullifier.toBuffer(); + } + + isEmpty(): boolean { + return this.nullifier.isZero(); + } + + updateTo(_another: NullifierLeaf): NullifierLeaf { + throw new Error('Nullifiers are create only'); + } + + static buildDummy(key: bigint): NullifierLeaf { + return new NullifierLeaf(new Fr(key)); + } + + static fromBuffer(buf: Buffer): NullifierLeaf { + return new NullifierLeaf(Fr.fromBuffer(buf)); + } +} diff --git a/yarn-project/circuits.js/src/structs/rollup/public_data_leaf/index.ts b/yarn-project/circuits.js/src/structs/rollup/public_data_leaf/index.ts new file mode 100644 index 000000000000..cad133b2ff90 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/rollup/public_data_leaf/index.ts @@ -0,0 +1,129 @@ +import { toBigIntBE, toBufferBE } from '@aztec/foundation/bigint-buffer'; +import { Fr } from '@aztec/foundation/fields'; +import { BufferReader } from '@aztec/foundation/serialize'; +import { IndexedTreeLeaf, IndexedTreeLeafPreimage } from '@aztec/foundation/trees'; + +import { serializeToBuffer } from '../../../utils/serialize.js'; + +/** + * Class containing the data of a preimage of a single leaf in the public data tree. + * Note: It's called preimage because this data gets hashed before being inserted as a node into the `IndexedTree`. + */ +export class PublicDataTreeLeafPreimage implements IndexedTreeLeafPreimage { + constructor( + /** + * The slot of the leaf + */ + public slot: Fr, + /** + * The value of the leaf + */ + public value: Fr, + /** + * Next value inside the indexed tree's linked list. + */ + public nextSlot: Fr, + /** + * Index of the next leaf in the indexed tree's linked list. + */ + public nextIndex: bigint, + ) {} + + getKey(): bigint { + return this.slot.toBigInt(); + } + + getNextKey(): bigint { + return this.nextSlot.toBigInt(); + } + + getNextIndex(): bigint { + return this.nextIndex; + } + + asLeaf(): PublicDataTreeLeaf { + return new PublicDataTreeLeaf(this.slot, this.value); + } + + toBuffer(): Buffer { + return Buffer.concat(this.toHashInputs()); + } + + toHashInputs(): Buffer[] { + return [ + Buffer.from(this.slot.toBuffer()), + Buffer.from(this.value.toBuffer()), + Buffer.from(toBufferBE(this.nextIndex, 32)), + Buffer.from(this.nextSlot.toBuffer()), + ]; + } + + clone(): PublicDataTreeLeafPreimage { + return new PublicDataTreeLeafPreimage(this.slot, this.value, this.nextSlot, this.nextIndex); + } + + static empty(): PublicDataTreeLeafPreimage { + return new PublicDataTreeLeafPreimage(Fr.ZERO, Fr.ZERO, Fr.ZERO, 0n); + } + + static fromBuffer(buffer: Buffer): PublicDataTreeLeafPreimage { + const reader = BufferReader.asReader(buffer); + const slot = Fr.fromBuffer(reader); + const value = Fr.fromBuffer(reader); + const nextIndex = toBigIntBE(reader.readBytes(32)); + const nextSlot = Fr.fromBuffer(reader); + return new PublicDataTreeLeafPreimage(slot, value, nextSlot, nextIndex); + } + + static fromLeaf(leaf: PublicDataTreeLeaf, nextKey: bigint, nextIndex: bigint): PublicDataTreeLeafPreimage { + return new PublicDataTreeLeafPreimage(leaf.slot, leaf.value, new Fr(nextKey), nextIndex); + } + + static clone(preimage: PublicDataTreeLeafPreimage): PublicDataTreeLeafPreimage { + return new PublicDataTreeLeafPreimage(preimage.slot, preimage.value, preimage.nextSlot, preimage.nextIndex); + } +} + +/** + * A leaf in the public data indexed tree. + */ +export class PublicDataTreeLeaf implements IndexedTreeLeaf { + constructor( + /** + * The slot the value is stored in + */ + public slot: Fr, + /** + * The value stored in the slot + */ + public value: Fr, + ) {} + + getKey(): bigint { + return this.slot.toBigInt(); + } + + toBuffer() { + return serializeToBuffer([this.slot, this.value]); + } + + static fromBuffer(buffer: Buffer | BufferReader) { + const reader = BufferReader.asReader(buffer); + return new PublicDataTreeLeaf(Fr.fromBuffer(reader), Fr.fromBuffer(reader)); + } + + isEmpty(): boolean { + return this.slot.isZero() && this.value.isZero(); + } + + updateTo(another: PublicDataTreeLeaf): PublicDataTreeLeaf { + if (!this.slot.equals(another.slot)) { + throw new Error('Invalid update: slots do not match'); + } + return new PublicDataTreeLeaf(this.slot, another.value); + } + + static buildDummy(key: bigint): PublicDataTreeLeaf { + return new PublicDataTreeLeaf(new Fr(key), new Fr(0)); + } +} diff --git a/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts b/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts index ddcb144be638..1c590cd15826 100644 --- a/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts +++ b/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts @@ -111,13 +111,13 @@ export class RootRollupPublicInputs { public endContractTreeSnapshot: AppendOnlyTreeSnapshot, /** - * Root of the public data tree at the start of the rollup. + * Snapshot of the public data tree at the start of the rollup. */ - public startPublicDataTreeRoot: Fr, + public startPublicDataTreeSnapshot: AppendOnlyTreeSnapshot, /** - * Root of the public data tree at the end of the rollup. + * Snapshot of the public data tree at the end of the rollup. */ - public endPublicDataTreeRoot: Fr, + public endPublicDataTreeSnapshot: AppendOnlyTreeSnapshot, /** * Snapshot of the L1 to L2 message tree at the start of the rollup. @@ -157,8 +157,8 @@ export class RootRollupPublicInputs { fields.endNullifierTreeSnapshot, fields.startContractTreeSnapshot, fields.endContractTreeSnapshot, - fields.startPublicDataTreeRoot, - fields.endPublicDataTreeRoot, + fields.startPublicDataTreeSnapshot, + fields.endPublicDataTreeSnapshot, fields.startL1ToL2MessagesTreeSnapshot, fields.endL1ToL2MessagesTreeSnapshot, fields.startArchiveSnapshot, @@ -209,8 +209,8 @@ export class RootRollupPublicInputs { reader.readObject(AppendOnlyTreeSnapshot), reader.readObject(AppendOnlyTreeSnapshot), reader.readObject(AppendOnlyTreeSnapshot), - Fr.fromBuffer(reader), - Fr.fromBuffer(reader), + reader.readObject(AppendOnlyTreeSnapshot), + reader.readObject(AppendOnlyTreeSnapshot), reader.readObject(AppendOnlyTreeSnapshot), reader.readObject(AppendOnlyTreeSnapshot), reader.readObject(AppendOnlyTreeSnapshot), diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index 7c39a943fda7..87ab8986361d 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -48,10 +48,8 @@ import { MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX, MAX_PUBLIC_CALL_STACK_LENGTH_PER_CALL, MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, - MAX_PUBLIC_DATA_READS_PER_BASE_ROLLUP, MAX_PUBLIC_DATA_READS_PER_CALL, MAX_PUBLIC_DATA_READS_PER_TX, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_BASE_ROLLUP, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_READ_REQUESTS_PER_CALL, @@ -67,6 +65,7 @@ import { NewContractData, NullifierLeafPreimage, OptionallyRevealedData, + PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, PUBLIC_DATA_TREE_HEIGHT, Point, PreviousKernelData, @@ -83,6 +82,8 @@ import { PublicCallStackItem, PublicCircuitPublicInputs, PublicDataRead, + PublicDataTreeLeaf, + PublicDataTreeLeafPreimage, PublicDataUpdateRequest, PublicKernelInputs, RETURN_VALUES_LENGTH, @@ -802,8 +803,8 @@ export function makeBaseOrMergeRollupPublicInputs( makeAppendOnlyTreeSnapshot(seed + 0x600), makeAppendOnlyTreeSnapshot(seed + 0x700), makeAppendOnlyTreeSnapshot(seed + 0x800), - fr(seed + 0x900), - fr(seed + 0x1000), + makeAppendOnlyTreeSnapshot(seed + 0x900), + makeAppendOnlyTreeSnapshot(seed + 0x1000), [fr(seed + 0x901), fr(seed + 0x902)], ); } @@ -864,8 +865,8 @@ export function makeRootRollupPublicInputs( endNullifierTreeSnapshot: makeAppendOnlyTreeSnapshot((seed += 0x100)), startContractTreeSnapshot: makeAppendOnlyTreeSnapshot((seed += 0x100)), endContractTreeSnapshot: makeAppendOnlyTreeSnapshot((seed += 0x100)), - startPublicDataTreeRoot: fr((seed += 0x100)), - endPublicDataTreeRoot: fr((seed += 0x100)), + startPublicDataTreeSnapshot: makeAppendOnlyTreeSnapshot((seed += 0x100)), + endPublicDataTreeSnapshot: makeAppendOnlyTreeSnapshot((seed += 0x100)), startL1ToL2MessagesTreeSnapshot: makeAppendOnlyTreeSnapshot((seed += 0x100)), endL1ToL2MessagesTreeSnapshot: makeAppendOnlyTreeSnapshot((seed += 0x100)), startArchiveSnapshot: makeAppendOnlyTreeSnapshot((seed += 0x100)), @@ -884,6 +885,24 @@ export function makeMergeRollupInputs(seed = 0): MergeRollupInputs { return new MergeRollupInputs([makePreviousRollupData(seed), makePreviousRollupData(seed + 0x1000)]); } +/** + * Makes arbitrary public data tree leaves. + * @param seed - The seed to use for generating the public data tree leaf. + * @returns A public data tree leaf. + */ +export function makePublicDataTreeLeaf(seed = 0): PublicDataTreeLeaf { + return new PublicDataTreeLeaf(new Fr(seed), new Fr(seed + 1)); +} + +/** + * Makes arbitrary public data tree leaf preimages. + * @param seed - The seed to use for generating the public data tree leaf preimage. + * @returns A public data tree leaf preimage. + */ +export function makePublicDataTreeLeafPreimage(seed = 0): PublicDataTreeLeafPreimage { + return new PublicDataTreeLeafPreimage(new Fr(seed), new Fr(seed + 1), new Fr(seed + 2), BigInt(seed + 3)); +} + /** * Makes arbitrary base rollup inputs. * @param seed - The seed to use for generating the base rollup inputs. @@ -895,7 +914,7 @@ export function makeBaseRollupInputs(seed = 0): BaseRollupInputs { const startNoteHashTreeSnapshot = makeAppendOnlyTreeSnapshot(seed + 0x100); const startNullifierTreeSnapshot = makeAppendOnlyTreeSnapshot(seed + 0x200); const startContractTreeSnapshot = makeAppendOnlyTreeSnapshot(seed + 0x300); - const startPublicDataTreeRoot = fr(seed + 0x400); + const startPublicDataTreeSnapshot = makeAppendOnlyTreeSnapshot(seed + 0x400); const startArchiveSnapshot = makeAppendOnlyTreeSnapshot(seed + 0x500); const lowNullifierLeafPreimages = makeTuple( @@ -917,16 +936,69 @@ export function makeBaseRollupInputs(seed = 0): BaseRollupInputs { const sortedNewNullifiers = makeTuple(MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP, fr, seed + 0x6000); const sortednewNullifiersIndexes = makeTuple(MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP, i => i, seed + 0x7000); - const newPublicDataUpdateRequestsSiblingPaths = makeTuple( - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_BASE_ROLLUP, - x => makeTuple(PUBLIC_DATA_TREE_HEIGHT, fr, x), - seed + 0x8000, + const sortedPublicDataWrites = makeTuple( + KERNELS_PER_BASE_ROLLUP, + i => { + return makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, makePublicDataTreeLeaf, seed + 0x8000 + i * 0x100); + }, + 0, + ); + const sortedPublicDataWritesIndexes = makeTuple( + KERNELS_PER_BASE_ROLLUP, + () => makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => i, 0), + 0, ); - const newPublicDataReadsSiblingPaths = makeTuple( - MAX_PUBLIC_DATA_READS_PER_BASE_ROLLUP, - x => makeTuple(PUBLIC_DATA_TREE_HEIGHT, fr, x), - seed + 0x8000, + const lowPublicDataWritesPreimages = makeTuple( + KERNELS_PER_BASE_ROLLUP, + i => { + return makeTuple( + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + makePublicDataTreeLeafPreimage, + seed + 0x8200 + i * 0x100, + ); + }, + 0, + ); + + const lowPublicDataWritesMembershipWitnesses = makeTuple( + KERNELS_PER_BASE_ROLLUP, + i => { + return makeTuple( + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + i => makeMembershipWitness(PUBLIC_DATA_TREE_HEIGHT, i), + seed + 0x8400 + i * 0x100, + ); + }, + 0, + ); + + const publicDataWritesSubtreeSiblingPaths = makeTuple( + KERNELS_PER_BASE_ROLLUP, + i => { + return makeTuple(PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, fr, 0x8600 + i * 0x100); + }, + 0, + ); + + const publicDataReadsPreimages = makeTuple( + KERNELS_PER_BASE_ROLLUP, + i => { + return makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, makePublicDataTreeLeafPreimage, seed + 0x8800 + i * 0x100); + }, + 0, + ); + + const publicDataReadsMembershipWitnesses = makeTuple( + KERNELS_PER_BASE_ROLLUP, + i => { + return makeTuple( + MAX_PUBLIC_DATA_READS_PER_TX, + i => makeMembershipWitness(PUBLIC_DATA_TREE_HEIGHT, i), + seed + 0x8a00 + i * 0x100, + ); + }, + 0, ); const archiveRootMembershipWitnesses = makeTuple(KERNELS_PER_BASE_ROLLUP, x => @@ -941,7 +1013,7 @@ export function makeBaseRollupInputs(seed = 0): BaseRollupInputs { startNoteHashTreeSnapshot, startNullifierTreeSnapshot, startContractTreeSnapshot, - startPublicDataTreeRoot, + startPublicDataTreeSnapshot, archiveSnapshot: startArchiveSnapshot, sortedNewNullifiers, sortednewNullifiersIndexes, @@ -949,8 +1021,13 @@ export function makeBaseRollupInputs(seed = 0): BaseRollupInputs { newCommitmentsSubtreeSiblingPath, newNullifiersSubtreeSiblingPath, newContractsSubtreeSiblingPath, - newPublicDataUpdateRequestsSiblingPaths, - newPublicDataReadsSiblingPaths, + sortedPublicDataWrites, + sortedPublicDataWritesIndexes, + lowPublicDataWritesPreimages, + lowPublicDataWritesMembershipWitnesses, + publicDataWritesSubtreeSiblingPaths, + publicDataReadsPreimages, + publicDataReadsMembershipWitnesses, archiveRootMembershipWitnesses, constants, }); diff --git a/yarn-project/end-to-end/src/e2e_account_contracts.test.ts b/yarn-project/end-to-end/src/e2e_account_contracts.test.ts index ee501fa57e72..fcc6f9ea6283 100644 --- a/yarn-project/end-to-end/src/e2e_account_contracts.test.ts +++ b/yarn-project/end-to-end/src/e2e_account_contracts.test.ts @@ -58,7 +58,7 @@ function itShouldBehaveLikeAnAccountContract( logger('Calling public function...'); await child.methods.pubIncValue(42).send().wait({ interval: 0.1 }); const storedValue = await pxe.getPublicStorageAt(child.address, new Fr(1)); - expect(storedValue!).toEqual(new Fr(42n)); + expect(storedValue).toEqual(new Fr(42n)); }, 60_000); it('fails to call a function using an invalid signature', async () => { diff --git a/yarn-project/end-to-end/src/e2e_inclusion_proofs_contract.test.ts b/yarn-project/end-to-end/src/e2e_inclusion_proofs_contract.test.ts index 1feedd2864c4..da4ab9fbb085 100644 --- a/yarn-project/end-to-end/src/e2e_inclusion_proofs_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_inclusion_proofs_contract.test.ts @@ -137,7 +137,7 @@ describe('e2e_inclusion_proofs_contract', () => { const randomPublicValue = Fr.random(); await expect( contract.methods.test_public_value_inclusion_proof(randomPublicValue, blockNumber).send().wait(), - ).rejects.toThrow(/Proving public value inclusion failed/); + ).rejects.toThrow(/Public value does not match value in witness/); }); it('proves existence of a nullifier in private context', async () => { diff --git a/yarn-project/end-to-end/src/e2e_ordering.test.ts b/yarn-project/end-to-end/src/e2e_ordering.test.ts index 7d30abe9e747..452d17172d6d 100644 --- a/yarn-project/end-to-end/src/e2e_ordering.test.ts +++ b/yarn-project/end-to-end/src/e2e_ordering.test.ts @@ -76,7 +76,7 @@ describe('e2e_ordering', () => { // The final value of the child is the last one set const value = await pxe.getPublicStorageAt(child.address, new Fr(1)); - expect(value?.value).toBe(expectedOrder[1]); // final state should match last value set + expect(value.value).toBe(expectedOrder[1]); // final state should match last value set }, ); }); @@ -100,7 +100,7 @@ describe('e2e_ordering', () => { expect(receipt.status).toBe(TxStatus.MINED); const value = await pxe.getPublicStorageAt(child.address, new Fr(1)); - expect(value?.value).toBe(expectedOrder[1]); // final state should match last value set + expect(value.value).toBe(expectedOrder[1]); // final state should match last value set }, ); diff --git a/yarn-project/end-to-end/src/guides/dapp_testing.test.ts b/yarn-project/end-to-end/src/guides/dapp_testing.test.ts index 3b11334e8948..2f0a34fd38b1 100644 --- a/yarn-project/end-to-end/src/guides/dapp_testing.test.ts +++ b/yarn-project/end-to-end/src/guides/dapp_testing.test.ts @@ -165,7 +165,7 @@ describe('guides/dapp/testing', () => { await token.methods.mint_public(owner.getAddress(), 100n).send().wait(); const ownerPublicBalanceSlot = cheats.aztec.computeSlotInMap(6n, owner.getAddress()); const balance = await pxe.getPublicStorageAt(token.address, ownerPublicBalanceSlot); - expect(balance!.value).toEqual(100n); + expect(balance.value).toEqual(100n); // docs:end:public-storage }, 30_000); diff --git a/yarn-project/foundation/src/trees/index.ts b/yarn-project/foundation/src/trees/index.ts index 030a59f2570b..195877aec3e5 100644 --- a/yarn-project/foundation/src/trees/index.ts +++ b/yarn-project/foundation/src/trees/index.ts @@ -14,6 +14,12 @@ export interface IndexedTreeLeaf { * Returns true if the leaf is empty. */ isEmpty(): boolean; + /** + * Updates the leaf with the data of another leaf. + * @param another - The leaf to update to. + * @returns The updated leaf. + */ + updateTo(another: IndexedTreeLeaf): IndexedTreeLeaf; } /** diff --git a/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts b/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts index 325b438a0f14..ff256dfcbf5a 100644 --- a/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts +++ b/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts @@ -43,6 +43,14 @@ export interface PreimageFactory { clone(preimage: IndexedTreeLeafPreimage): IndexedTreeLeafPreimage; } +export const buildDbKeyForPreimage = (name: string, index: bigint) => { + return `${name}:leaf_by_index:${toBufferBE(index, 32).toString('hex')}`; +}; + +export const buildDbKeyForLeafIndex = (name: string, key: bigint) => { + return `${name}:leaf_index_by_leaf_key:${toBufferBE(key, 32).toString('hex')}`; +}; + /** * Factory for creating leaves. */ @@ -59,14 +67,6 @@ export interface LeafFactory { fromBuffer(buffer: Buffer): IndexedTreeLeaf; } -export const buildDbKeyForPreimage = (name: string, index: bigint) => { - return `${name}:leaf_by_index:${toBufferBE(index, 32).toString('hex')}`; -}; - -export const buildDbKeyForLeafIndex = (name: string, key: bigint) => { - return `${name}:leaf_index_by_leaf_key:${toBufferBE(key, 32).toString('hex')}`; -}; - /** * Pre-compute empty witness. * @param treeHeight - Height of tree for sibling path. @@ -460,7 +460,41 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree { * nextIdx 4 2 3 7 5 1 0 6 * nextVal 2 10 15 19 3 5 0 20 * - * TODO: this implementation will change once the zero value is changed from h(0,0,0). Changes incoming over the next sprint + * For leaves that allow updating the process is exactly the same. When a leaf is inserted that is already present, + * the low leaf will be the leaf that is being updated, and it'll get updated and an empty leaf will be inserted instead. + * For example: + * + * Initial state: + * + * index 0 1 2 3 4 5 6 7 + * --------------------------------------------------------------------- + * slot 0 0 0 0 0 0 0 0 + * value 0 0 0 0 0 0 0 0 + * nextIdx 0 0 0 0 0 0 0 0 + * nextSlot 0 0 0 0 0 0 0 0. + * + * + * Add new value 30:5: + * + * index 0 1 2 3 4 5 6 7 + * --------------------------------------------------------------------- + * slot 0 30 0 0 0 0 0 0 + * value 0 5 0 0 0 0 0 0 + * nextIdx 1 0 0 0 0 0 0 0 + * nextSlot 30 0 0 0 0 0 0 0. + * + * + * Update the value of 30 to 10 (insert 30:10): + * + * index 0 1 2 3 4 5 6 7 + * --------------------------------------------------------------------- + * slot 0 30 0 0 0 0 0 0 + * value 0 10 0 0 0 0 0 0 + * nextIdx 1 0 0 0 0 0 0 0 + * nextSlot 30 0 0 0 0 0 0 0. + * + * The low leaf is 30, so we update it to 10, and insert an empty leaf at index 2. + * * @param leaves - Values to insert into the tree. * @param subtreeHeight - Height of the subtree. * @returns The data for the leaves to be updated when inserting the new ones. @@ -473,6 +507,7 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree { leaves: Buffer[], subtreeHeight: SubtreeHeight, ): Promise> { + const insertedKeys = new Map(); const emptyLowLeafWitness = getEmptyLowLeafWitness(this.getDepth() as TreeHeight, this.leafPreimageFactory); // Accumulators const lowLeavesWitnesses: LowLeafWitnessData[] = leaves.map(() => emptyLowLeafWitness); @@ -496,6 +531,12 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree { continue; } + if (insertedKeys.has(newLeaf.getKey())) { + throw new Error('Cannot insert duplicated keys in the same batch'); + } else { + insertedKeys.set(newLeaf.getKey(), true); + } + const indexOfPrevious = await this.findIndexOfPreviousKey(newLeaf.getKey(), true); if (indexOfPrevious === undefined) { return { @@ -506,6 +547,8 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree { }; } + const isUpdate = indexOfPrevious.alreadyPresent; + // get the low leaf (existence checked in getting index) const lowLeafPreimage = (await this.getLatestLeafPreimageCopy(indexOfPrevious.index, true))!; const siblingPath = await this.getSiblingPath(BigInt(indexOfPrevious.index), true); @@ -519,23 +562,35 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree { // Update the running paths lowLeavesWitnesses[i] = witness; - const currentPendingPreimageLeaf = this.leafPreimageFactory.fromLeaf( - newLeaf, - lowLeafPreimage.getNextKey(), - lowLeafPreimage.getNextIndex(), - ); + if (isUpdate) { + const newLowLeaf = lowLeafPreimage.asLeaf().updateTo(newLeaf); - pendingInsertionSubtree[originalIndex] = currentPendingPreimageLeaf; + const newLowLeafPreimage = this.leafPreimageFactory.fromLeaf( + newLowLeaf, + lowLeafPreimage.getNextKey(), + lowLeafPreimage.getNextIndex(), + ); - const newLowLeafPreimage = this.leafPreimageFactory.fromLeaf( - lowLeafPreimage.asLeaf(), - newLeaf.getKey(), - startInsertionIndex + BigInt(originalIndex), - ); + await this.updateLeaf(newLowLeafPreimage, indexOfPrevious.index); - const lowLeafIndex = indexOfPrevious.index; - this.cachedLeafPreimages[lowLeafIndex.toString()] = newLowLeafPreimage; - await this.updateLeaf(newLowLeafPreimage, lowLeafIndex); + pendingInsertionSubtree[originalIndex] = this.leafPreimageFactory.empty(); + } else { + const newLowLeafPreimage = this.leafPreimageFactory.fromLeaf( + lowLeafPreimage.asLeaf(), + newLeaf.getKey(), + startInsertionIndex + BigInt(originalIndex), + ); + + await this.updateLeaf(newLowLeafPreimage, indexOfPrevious.index); + + const currentPendingPreimageLeaf = this.leafPreimageFactory.fromLeaf( + newLeaf, + lowLeafPreimage.getNextKey(), + lowLeafPreimage.getNextIndex(), + ); + + pendingInsertionSubtree[originalIndex] = currentPendingPreimageLeaf; + } } const newSubtreeSiblingPath = await this.getSubtreeSiblingPath( diff --git a/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree.test.ts b/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree.test.ts index 9ebc8c304722..470355f1e953 100644 --- a/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree.test.ts +++ b/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree.test.ts @@ -1,4 +1,10 @@ -import { Fr, NullifierLeaf, NullifierLeafPreimage } from '@aztec/circuits.js'; +import { + Fr, + NullifierLeaf, + NullifierLeafPreimage, + PublicDataTreeLeaf, + PublicDataTreeLeafPreimage, +} from '@aztec/circuits.js'; import { toBufferBE } from '@aztec/foundation/bigint-buffer'; import { Hasher, SiblingPath } from '@aztec/types'; @@ -15,6 +21,12 @@ class NullifierTree extends StandardIndexedTreeWithAppend { } } +class PublicDataTree extends StandardIndexedTreeWithAppend { + constructor(db: levelup.LevelUp, hasher: Hasher, name: string, depth: number, size: bigint = 0n, root?: Buffer) { + super(db, hasher, name, depth, size, PublicDataTreeLeafPreimage, PublicDataTreeLeaf, root); + } +} + const createDb = async (levelUp: levelup.LevelUp, hasher: Hasher, name: string, depth: number, prefilledSize = 1) => { return await newTree(NullifierTree, levelUp, hasher, name, depth, prefilledSize); }; @@ -23,10 +35,23 @@ const createFromName = async (levelUp: levelup.LevelUp, hasher: Hasher, name: st return await loadTree(NullifierTree, levelUp, hasher, name); }; -const createIndexedTreeLeafHashInputs = (value: number, nextIndex: number, nextValue: number) => { +const createNullifierTreeLeafHashInputs = (value: number, nextIndex: number, nextValue: number) => { return new NullifierLeafPreimage(new Fr(value), new Fr(nextValue), BigInt(nextIndex)).toHashInputs(); }; +const createPublicDataTreeLeaf = (slot: number, value: number) => { + return new PublicDataTreeLeaf(new Fr(slot), new Fr(value)); +}; + +const createPublicDataTreeLeafHashInputs = (slot: number, value: number, nextIndex: number, nextSlot: number) => { + return new PublicDataTreeLeafPreimage( + new Fr(slot), + new Fr(value), + new Fr(nextSlot), + BigInt(nextIndex), + ).toHashInputs(); +}; + const verifyCommittedState = async ( tree: MerkleTree, root: Buffer, @@ -64,7 +89,7 @@ describe('StandardIndexedTreeSpecific', () => { * nextVal 0 0 0 0 0 0 0 0. */ - const initialLeafHash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(0, 0, 0)); + const initialLeafHash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(0, 0, 0)); const level1ZeroHash = pedersen.hash(INITIAL_LEAF, INITIAL_LEAF); const level2ZeroHash = pedersen.hash(level1ZeroHash, level1ZeroHash); @@ -98,8 +123,8 @@ describe('StandardIndexedTreeSpecific', () => { * nextIdx 1 0 0 0 0 0 0 0 * nextVal 30 0 0 0 0 0 0 0. */ - index0Hash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(0, 1, 30)); - let index1Hash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(30, 0, 0)); + index0Hash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(0, 1, 30)); + let index1Hash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(30, 0, 0)); e10 = pedersen.hash(index0Hash, index1Hash); e20 = pedersen.hash(e10, level1ZeroHash); root = pedersen.hash(e20, level2ZeroHash); @@ -125,8 +150,8 @@ describe('StandardIndexedTreeSpecific', () => { * nextIdx 2 0 1 0 0 0 0 0 * nextVal 10 0 30 0 0 0 0 0. */ - index0Hash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(0, 2, 10)); - let index2Hash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(10, 1, 30)); + index0Hash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(0, 2, 10)); + let index2Hash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(10, 1, 30)); e10 = pedersen.hash(index0Hash, index1Hash); let e11 = pedersen.hash(index2Hash, INITIAL_LEAF); e20 = pedersen.hash(e10, e11); @@ -158,8 +183,8 @@ describe('StandardIndexedTreeSpecific', () => { * nextVal 10 0 20 30 0 0 0 0. */ e10 = pedersen.hash(index0Hash, index1Hash); - index2Hash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(10, 3, 20)); - const index3Hash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(20, 1, 30)); + index2Hash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(10, 3, 20)); + const index3Hash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(20, 1, 30)); e11 = pedersen.hash(index2Hash, index3Hash); e20 = pedersen.hash(e10, e11); root = pedersen.hash(e20, level2ZeroHash); @@ -189,8 +214,8 @@ describe('StandardIndexedTreeSpecific', () => { * nextIdx 2 4 3 1 0 0 0 0 * nextVal 10 50 20 30 0 0 0 0. */ - index1Hash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(30, 4, 50)); - const index4Hash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(50, 0, 0)); + index1Hash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(30, 4, 50)); + const index4Hash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(50, 0, 0)); e10 = pedersen.hash(index0Hash, index1Hash); e20 = pedersen.hash(e10, e11); const e12 = pedersen.hash(index4Hash, INITIAL_LEAF); @@ -262,7 +287,7 @@ describe('StandardIndexedTreeSpecific', () => { */ const INITIAL_LEAF = toBufferBE(0n, 32); - const initialLeafHash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(0, 0, 0)); + const initialLeafHash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(0, 0, 0)); const level1ZeroHash = pedersen.hash(INITIAL_LEAF, INITIAL_LEAF); const level2ZeroHash = pedersen.hash(level1ZeroHash, level1ZeroHash); let index0Hash = initialLeafHash; @@ -296,8 +321,8 @@ describe('StandardIndexedTreeSpecific', () => { * nextIdx 1 0 0 0 0 0 0 0 * nextVal 30 0 0 0 0 0 0 0. */ - index0Hash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(0, 1, 30)); - let index1Hash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(30, 0, 0)); + index0Hash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(0, 1, 30)); + let index1Hash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(30, 0, 0)); e10 = pedersen.hash(index0Hash, index1Hash); e20 = pedersen.hash(e10, level1ZeroHash); root = pedersen.hash(e20, level2ZeroHash); @@ -322,8 +347,8 @@ describe('StandardIndexedTreeSpecific', () => { * nextIdx 2 0 1 0 0 0 0 0 * nextVal 10 0 30 0 0 0 0 0. */ - index0Hash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(0, 2, 10)); - let index2Hash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(10, 1, 30)); + index0Hash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(0, 2, 10)); + let index2Hash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(10, 1, 30)); e10 = pedersen.hash(index0Hash, index1Hash); let e11 = pedersen.hash(index2Hash, INITIAL_LEAF); e20 = pedersen.hash(e10, e11); @@ -355,8 +380,8 @@ describe('StandardIndexedTreeSpecific', () => { * nextVal 10 0 20 30 0 0 0 0. */ e10 = pedersen.hash(index0Hash, index1Hash); - index2Hash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(10, 3, 20)); - const index3Hash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(20, 1, 30)); + index2Hash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(10, 3, 20)); + const index3Hash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(20, 1, 30)); e11 = pedersen.hash(index2Hash, index3Hash); e20 = pedersen.hash(e10, e11); root = pedersen.hash(e20, level2ZeroHash); @@ -394,8 +419,8 @@ describe('StandardIndexedTreeSpecific', () => { * nextIdx 2 6 3 1 0 0 0 0 * nextVal 10 50 20 30 0 0 0 0. */ - index1Hash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(30, 6, 50)); - const index6Hash = pedersen.hashInputs(createIndexedTreeLeafHashInputs(50, 0, 0)); + index1Hash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(30, 6, 50)); + const index6Hash = pedersen.hashInputs(createNullifierTreeLeafHashInputs(50, 0, 0)); e10 = pedersen.hash(index0Hash, index1Hash); e20 = pedersen.hash(e10, e11); const e13 = pedersen.hash(index6Hash, INITIAL_LEAF); @@ -492,4 +517,136 @@ describe('StandardIndexedTreeSpecific', () => { expect(await tree.findLeafIndex(values[0], false)).toBeDefined(); }); + + describe('Updatable leaves', () => { + it('should be able to upsert leaves', async () => { + // Create a depth-3 indexed merkle tree + const db = levelup(createMemDown()); + const tree = await newTree(PublicDataTree, db, pedersen, 'test', 3, 1); + + /** + * Initial state: + * + * index 0 1 2 3 4 5 6 7 + * --------------------------------------------------------------------- + * slot 0 0 0 0 0 0 0 0 + * value 0 0 0 0 0 0 0 0 + * nextIdx 0 0 0 0 0 0 0 0 + * nextSlot 0 0 0 0 0 0 0 0. + */ + + const EMPTY_LEAF = toBufferBE(0n, 32); + const initialLeafHash = pedersen.hashInputs(createPublicDataTreeLeafHashInputs(0, 0, 0, 0)); + const level1ZeroHash = pedersen.hash(EMPTY_LEAF, EMPTY_LEAF); + const level2ZeroHash = pedersen.hash(level1ZeroHash, level1ZeroHash); + let index0Hash = initialLeafHash; + + let e10 = pedersen.hash(index0Hash, EMPTY_LEAF); + let e20 = pedersen.hash(e10, level1ZeroHash); + + const inite10 = e10; + + let root = pedersen.hash(e20, level2ZeroHash); + const initialRoot = root; + + const emptySiblingPath = new SiblingPath(TEST_TREE_DEPTH, [EMPTY_LEAF, level1ZeroHash, level2ZeroHash]); + const initialSiblingPath = new SiblingPath(TEST_TREE_DEPTH, [initialLeafHash, level1ZeroHash, level2ZeroHash]); + + expect(tree.getRoot(true)).toEqual(root); + expect(tree.getNumLeaves(true)).toEqual(1n); + expect(await tree.getSiblingPath(0n, true)).toEqual( + new SiblingPath(TEST_TREE_DEPTH, [EMPTY_LEAF, level1ZeroHash, level2ZeroHash]), + ); + + await verifyCommittedState(tree, initialRoot, 0n, emptySiblingPath); + + /** + * Add new value 30:5: + * + * index 0 1 2 3 4 5 6 7 + * --------------------------------------------------------------------- + * slot 0 30 0 0 0 0 0 0 + * value 0 5 0 0 0 0 0 0 + * nextIdx 1 0 0 0 0 0 0 0 + * nextSlot 30 0 0 0 0 0 0 0. + */ + index0Hash = pedersen.hashInputs(createPublicDataTreeLeafHashInputs(0, 0, 1, 30)); + let index1Hash = pedersen.hashInputs(createPublicDataTreeLeafHashInputs(30, 5, 0, 0)); + e10 = pedersen.hash(index0Hash, index1Hash); + e20 = pedersen.hash(e10, level1ZeroHash); + root = pedersen.hash(e20, level2ZeroHash); + + await tree.appendLeaves([createPublicDataTreeLeaf(30, 5).toBuffer()]); + + expect(tree.getRoot(true)).toEqual(root); + expect(tree.getNumLeaves(true)).toEqual(2n); + expect(await tree.getSiblingPath(1n, true)).toEqual( + new SiblingPath(TEST_TREE_DEPTH, [index0Hash, level1ZeroHash, level2ZeroHash]), + ); + + // ensure the committed state is correct + await verifyCommittedState(tree, initialRoot, 1n, initialSiblingPath); + + /** + * Update the value of 30 to 10: + * + * index 0 1 2 3 4 5 6 7 + * --------------------------------------------------------------------- + * slot 0 30 0 0 0 0 0 0 + * value 0 10 0 0 0 0 0 0 + * nextIdx 1 0 0 0 0 0 0 0 + * nextSlot 30 0 0 0 0 0 0 0. + */ + index1Hash = pedersen.hashInputs(createPublicDataTreeLeafHashInputs(30, 10, 0, 0)); + e10 = pedersen.hash(index0Hash, index1Hash); + e20 = pedersen.hash(e10, level1ZeroHash); + root = pedersen.hash(e20, level2ZeroHash); + + await tree.appendLeaves([createPublicDataTreeLeaf(30, 10).toBuffer()]); + + expect(tree.getRoot(true)).toEqual(root); + expect(tree.getNumLeaves(true)).toEqual(3n); + expect(await tree.getSiblingPath(2n, true)).toEqual( + new SiblingPath(TEST_TREE_DEPTH, [EMPTY_LEAF, e10, level2ZeroHash]), + ); + + // ensure the committed state is correct + await verifyCommittedState( + tree, + initialRoot, + 2n, + new SiblingPath(TEST_TREE_DEPTH, [EMPTY_LEAF, inite10, level2ZeroHash]), + ); + }); + + it.each([ + [[createPublicDataTreeLeaf(1, 10)], [createPublicDataTreeLeaf(1, 20)]], + [[createPublicDataTreeLeaf(1, 10)], [createPublicDataTreeLeaf(1, 20), createPublicDataTreeLeaf(2, 5)]], + [ + [createPublicDataTreeLeaf(1, 10), createPublicDataTreeLeaf(2, 10)], + [createPublicDataTreeLeaf(1, 20), createPublicDataTreeLeaf(10, 50), createPublicDataTreeLeaf(2, 5)], + ], + ] as const)('performs batch upsert correctly', async (initialState, batch) => { + const TREE_HEIGHT = 16; + const INITIAL_TREE_SIZE = 8; + const SUBTREE_HEIGHT = 5; + + const db = levelup(createMemDown()); + const appendTree = await newTree(PublicDataTree, db, pedersen, 'test', TREE_HEIGHT, INITIAL_TREE_SIZE); + const insertTree = await newTree(PublicDataTree, db, pedersen, 'test', TREE_HEIGHT, INITIAL_TREE_SIZE); + + await appendTree.appendLeaves(initialState.map(leaf => leaf.toBuffer())); + await insertTree.appendLeaves(initialState.map(leaf => leaf.toBuffer())); + + await appendTree.appendLeaves(batch.map(leaf => leaf.toBuffer())); + await insertTree.batchInsert( + batch.map(leaf => leaf.toBuffer()), + SUBTREE_HEIGHT, + ); + + const expectedRoot = appendTree.getRoot(true); + const actualRoot = insertTree.getRoot(true); + expect(actualRoot).toEqual(expectedRoot); + }); + }); }); diff --git a/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree_with_append.ts b/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree_with_append.ts index 990f4e6ef5fa..3fd3a1230848 100644 --- a/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree_with_append.ts +++ b/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree_with_append.ts @@ -18,6 +18,14 @@ export class StandardIndexedTreeWithAppend extends StandardIndexedTree { } } + private appendEmptyLeaf() { + const newSize = (this.cachedSize ?? this.size) + 1n; + if (newSize - 1n > this.maxIndex) { + throw Error(`Can't append beyond max index. Max index: ${this.maxIndex}`); + } + this.cachedSize = newSize; + } + /** * Appends the given leaf to the tree. * @param leaf - The leaf to append. @@ -28,11 +36,7 @@ export class StandardIndexedTreeWithAppend extends StandardIndexedTree { // Special case when appending zero if (newLeaf.getKey() === 0n) { - const newSize = (this.cachedSize ?? this.size) + 1n; - if (newSize - 1n > this.maxIndex) { - throw Error(`Can't append beyond max index. Max index: ${this.maxIndex}`); - } - this.cachedSize = newSize; + this.appendEmptyLeaf(); return; } @@ -40,27 +44,36 @@ export class StandardIndexedTreeWithAppend extends StandardIndexedTree { if (lowLeafIndex === undefined) { throw new Error(`Previous leaf not found!`); } + + const isUpdate = lowLeafIndex.alreadyPresent; const lowLeafPreimage = (await this.getLatestLeafPreimageCopy(lowLeafIndex.index, true))!; + const currentSize = this.getNumLeaves(true); - const newLeafPreimage = this.leafPreimageFactory.fromLeaf( - newLeaf, - lowLeafPreimage.getNextKey(), - lowLeafPreimage.getNextIndex(), - ); + if (isUpdate) { + const newLowLeaf = lowLeafPreimage.asLeaf().updateTo(newLeaf); + const newLowLeafPreimage = this.leafPreimageFactory.fromLeaf( + newLowLeaf, + lowLeafPreimage.getNextKey(), + lowLeafPreimage.getNextIndex(), + ); - if (lowLeafIndex.alreadyPresent) { - return; + await this.updateLeaf(newLowLeafPreimage, BigInt(lowLeafIndex.index)); + this.appendEmptyLeaf(); + } else { + const newLeafPreimage = this.leafPreimageFactory.fromLeaf( + newLeaf, + lowLeafPreimage.getNextKey(), + lowLeafPreimage.getNextIndex(), + ); + + // insert a new leaf at the highest index and update the values of our previous leaf copy + const newLowLeafPreimage = this.leafPreimageFactory.fromLeaf( + lowLeafPreimage.asLeaf(), + newLeaf.getKey(), + BigInt(currentSize), + ); + await this.updateLeaf(newLowLeafPreimage, BigInt(lowLeafIndex.index)); + await this.updateLeaf(newLeafPreimage, currentSize); } - // insert a new leaf at the highest index and update the values of our previous leaf copy - const currentSize = this.getNumLeaves(true); - const newLowLeafPreimage = this.leafPreimageFactory.fromLeaf( - lowLeafPreimage.asLeaf(), - newLeaf.getKey(), - BigInt(currentSize), - ); - this.cachedLeafPreimages[Number(currentSize)] = newLeafPreimage; - this.cachedLeafPreimages[Number(lowLeafIndex.index)] = newLowLeafPreimage; - await this.updateLeaf(newLowLeafPreimage, BigInt(lowLeafIndex.index)); - await this.updateLeaf(newLeafPreimage, this.getNumLeaves(true)); } } diff --git a/yarn-project/noir-protocol-circuits/src/__snapshots__/index.test.ts.snap b/yarn-project/noir-protocol-circuits/src/__snapshots__/index.test.ts.snap index 850acabc9de2..23d63450fe63 100644 --- a/yarn-project/noir-protocol-circuits/src/__snapshots__/index.test.ts.snap +++ b/yarn-project/noir-protocol-circuits/src/__snapshots__/index.test.ts.snap @@ -17169,7 +17169,7 @@ KernelCircuitPublicInputs { ], "publicDataReads": [ PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -17252,7 +17252,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -17335,7 +17335,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -17418,7 +17418,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -17501,7 +17501,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -17584,7 +17584,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -17667,7 +17667,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -17750,7 +17750,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -17833,7 +17833,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -17916,7 +17916,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -17999,7 +17999,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -18082,7 +18082,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -18165,7 +18165,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -18248,7 +18248,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -18331,7 +18331,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -18414,7 +18414,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -18499,7 +18499,7 @@ KernelCircuitPublicInputs { ], "publicDataUpdateRequests": [ PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -18622,7 +18622,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -18745,7 +18745,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -18868,7 +18868,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -18991,7 +18991,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -19114,7 +19114,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -19237,7 +19237,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -19360,7 +19360,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -19483,7 +19483,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -19606,7 +19606,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -19729,7 +19729,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -19852,7 +19852,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -19975,7 +19975,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -20098,7 +20098,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -20221,7 +20221,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -20344,7 +20344,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -42785,7 +42785,7 @@ KernelCircuitPublicInputs { ], "publicDataReads": [ PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -42868,7 +42868,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -42951,7 +42951,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -43034,7 +43034,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -43117,7 +43117,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -43200,7 +43200,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -43283,7 +43283,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -43366,7 +43366,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -43449,7 +43449,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -43532,7 +43532,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -43615,7 +43615,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -43698,7 +43698,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -43781,7 +43781,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -43864,7 +43864,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -43947,7 +43947,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -44030,7 +44030,7 @@ KernelCircuitPublicInputs { }, }, PublicDataRead { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -44115,7 +44115,7 @@ KernelCircuitPublicInputs { ], "publicDataUpdateRequests": [ PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -44238,7 +44238,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -44361,7 +44361,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -44484,7 +44484,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -44607,7 +44607,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -44730,7 +44730,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -44853,7 +44853,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -44976,7 +44976,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -45099,7 +45099,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -45222,7 +45222,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -45345,7 +45345,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -45468,7 +45468,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -45591,7 +45591,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -45714,7 +45714,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -45837,7 +45837,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ @@ -45960,7 +45960,7 @@ KernelCircuitPublicInputs { "sideEffectCounter": undefined, }, PublicDataUpdateRequest { - "leafIndex": Fr { + "leafSlot": Fr { "asBigInt": 0n, "asBuffer": { "data": [ diff --git a/yarn-project/noir-protocol-circuits/src/crates/public-kernel-lib/src/common.nr b/yarn-project/noir-protocol-circuits/src/crates/public-kernel-lib/src/common.nr index 36c823682f0d..1a4be8812c4d 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/public-kernel-lib/src/common.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/public-kernel-lib/src/common.nr @@ -148,7 +148,7 @@ fn propagate_valid_public_data_update_requests(public_call: PublicCallData, circ let update_request = update_requests[i]; if (!update_request.is_empty()) { let public_data_update_request = PublicDataUpdateRequest { - leaf_index: compute_public_data_tree_index(contract_address, update_request.storage_slot), + leaf_slot: compute_public_data_tree_index(contract_address, update_request.storage_slot), old_value: compute_public_data_tree_value(update_request.old_value), new_value: compute_public_data_tree_value(update_request.new_value), }; @@ -169,7 +169,7 @@ fn propagate_valid_public_data_reads(public_call: PublicCallData, circuit_output let read_request: StorageRead = read_requests[i]; if !read_request.is_empty() { let public_data_read = PublicDataRead { - leaf_index: compute_public_data_tree_index(contract_address, read_request.storage_slot), + leaf_slot: compute_public_data_tree_index(contract_address, read_request.storage_slot), value: compute_public_data_tree_value(read_request.current_value), }; public_data_reads.push(public_data_read); diff --git a/yarn-project/noir-protocol-circuits/src/crates/public-kernel-lib/src/utils.nr b/yarn-project/noir-protocol-circuits/src/crates/public-kernel-lib/src/utils.nr index ac4994b83426..cada7fa7264e 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/public-kernel-lib/src/utils.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/public-kernel-lib/src/utils.nr @@ -22,7 +22,7 @@ pub fn compute_public_data_reads(contract_address: AztecAddress, read_request let read_request = read_requests[i]; if !read_request.is_empty() { public_data_reads[i] = PublicDataRead { - leaf_index: compute_public_data_tree_index(contract_address, read_request.storage_slot), + leaf_slot: compute_public_data_tree_index(contract_address, read_request.storage_slot), value: compute_public_data_tree_value(read_request.current_value), }; } @@ -51,7 +51,7 @@ pub fn compute_public_data_update_requests( let update_request = update_requests[i]; if !update_request.is_empty() { public_data_update_requests[i] = PublicDataUpdateRequest { - leaf_index: compute_public_data_tree_index(contract_address, update_request.storage_slot), + leaf_slot: compute_public_data_tree_index(contract_address, update_request.storage_slot), old_value: compute_public_data_tree_value(update_request.old_value), new_value: compute_public_data_tree_value(update_request.new_value), }; diff --git a/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/abis.nr b/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/abis.nr index 610e10e5e454..e8a3e182b10c 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/abis.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/abis.nr @@ -1,4 +1,5 @@ mod nullifier_leaf_preimage; +mod public_data_tree_leaf; mod append_only_tree_snapshot; mod global_variables; mod constant_rollup_data; diff --git a/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/abis/base_or_merge_rollup_public_inputs.nr b/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/abis/base_or_merge_rollup_public_inputs.nr index 4594e4ea4c3a..a0fca5beed4c 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/abis/base_or_merge_rollup_public_inputs.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/abis/base_or_merge_rollup_public_inputs.nr @@ -26,8 +26,8 @@ struct BaseOrMergeRollupPublicInputs { start_contract_tree_snapshot : AppendOnlyTreeSnapshot, end_contract_tree_snapshot : AppendOnlyTreeSnapshot, - start_public_data_tree_root : Field, - end_public_data_tree_root : Field, + start_public_data_tree_snapshot : AppendOnlyTreeSnapshot, + end_public_data_tree_snapshot : AppendOnlyTreeSnapshot, // We hash public inputs to make them constant-sized (to then be unpacked on-chain) // U128 isn't safe if it's an input to the circuit (it won't automatically constrain the witness) diff --git a/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/abis/public_data_tree_leaf.nr b/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/abis/public_data_tree_leaf.nr new file mode 100644 index 000000000000..31e800793615 --- /dev/null +++ b/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/abis/public_data_tree_leaf.nr @@ -0,0 +1,52 @@ +struct PublicDataTreeLeafPreimage { + slot : Field, + value: Field, + next_slot :Field, + next_index : u32, +} + +impl PublicDataTreeLeafPreimage { + pub fn default() -> Self { + Self { + slot: 0, + value: 0, + next_slot: 0, + next_index: 0, + } + } + + pub fn is_empty(self) -> bool { + (self.slot == 0) & (self.value == 0) & (self.next_slot == 0) & (self.next_index == 0) + } + + pub fn hash(self) -> Field { + if self.is_empty() { + 0 + } else { + dep::std::hash::pedersen_hash([self.slot, self.value, (self.next_index as Field), self.next_slot]) + } + } +} + +struct PublicDataTreeLeaf { + slot: Field, + value: Field, +} + + +impl PublicDataTreeLeaf { + pub fn default() -> Self { + Self { + slot: 0, + value: 0, + } + } + + pub fn is_empty(self) -> bool { + (self.slot == 0) & (self.value == 0) + } + + pub fn eq(self, other: Self) -> bool { + (self.slot == other.slot) & (self.value == other.value) + } +} diff --git a/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/base/base_rollup_inputs.nr b/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/base/base_rollup_inputs.nr index 6783a2047936..086a03f219a8 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/base/base_rollup_inputs.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/base/base_rollup_inputs.nr @@ -1,4 +1,5 @@ use crate::abis::nullifier_leaf_preimage::NullifierLeafPreimage; +use crate::abis::public_data_tree_leaf::{PublicDataTreeLeaf, PublicDataTreeLeafPreimage}; use crate::abis::append_only_tree_snapshot::AppendOnlyTreeSnapshot; use crate::abis::constant_rollup_data::ConstantRollupData; use crate::abis::base_or_merge_rollup_public_inputs::{BaseOrMergeRollupPublicInputs, BASE_ROLLUP_TYPE}; @@ -30,9 +31,11 @@ use dep::types::constants::{ NUM_UNENCRYPTED_LOGS_HASHES_PER_TX, NULLIFIER_SUBTREE_HEIGHT, NULLIFIER_TREE_HEIGHT, + PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, + PUBLIC_DATA_SUBTREE_HEIGHT, }; use dep::types::abis::previous_kernel_data::PreviousKernelData; -use dep::types::abis::membership_witness::{NullifierMembershipWitness, MembershipWitness}; +use dep::types::abis::membership_witness::{NullifierMembershipWitness, PublicDataMembershipWitness, MembershipWitness}; use dep::types::abis::membership_witness::ArchiveRootMembershipWitness; struct BaseRollupInputs { @@ -40,7 +43,7 @@ struct BaseRollupInputs { start_note_hash_tree_snapshot: AppendOnlyTreeSnapshot, start_nullifier_tree_snapshot: AppendOnlyTreeSnapshot, start_contract_tree_snapshot: AppendOnlyTreeSnapshot, - start_public_data_tree_root: Field, + start_public_data_tree_snapshot: AppendOnlyTreeSnapshot, archive_snapshot: AppendOnlyTreeSnapshot, sorted_new_nullifiers: [Field; MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP], @@ -52,10 +55,17 @@ struct BaseRollupInputs { // Note: the insertion leaf index can be derived from the above snapshots' `next_available_leaf_index` values. new_commitments_subtree_sibling_path: [Field; NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH], new_nullifiers_subtree_sibling_path: [Field; NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH], + public_data_writes_subtree_sibling_paths: [[Field; PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH]; KERNELS_PER_BASE_ROLLUP], new_contracts_subtree_sibling_path: [Field; CONTRACT_SUBTREE_SIBLING_PATH_LENGTH], - new_public_data_update_requests_sibling_paths: [[Field; PUBLIC_DATA_TREE_HEIGHT]; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_BASE_ROLLUP], - new_public_data_reads_sibling_paths: [[Field; PUBLIC_DATA_TREE_HEIGHT]; MAX_PUBLIC_DATA_READS_PER_BASE_ROLLUP], + + sorted_public_data_writes: [[PublicDataTreeLeaf; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; KERNELS_PER_BASE_ROLLUP], + sorted_public_data_writes_indexes: [[u32; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; KERNELS_PER_BASE_ROLLUP], + low_public_data_writes_preimages: [[PublicDataTreeLeafPreimage; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; KERNELS_PER_BASE_ROLLUP], + low_public_data_writes_witnesses: [[PublicDataMembershipWitness; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; KERNELS_PER_BASE_ROLLUP], + public_data_reads_preimages: [[PublicDataTreeLeafPreimage; MAX_PUBLIC_DATA_READS_PER_TX]; KERNELS_PER_BASE_ROLLUP], + public_data_reads_witnesses: [[PublicDataMembershipWitness; MAX_PUBLIC_DATA_READS_PER_TX]; KERNELS_PER_BASE_ROLLUP], + archive_root_membership_witnesses: [ArchiveRootMembershipWitness; KERNELS_PER_BASE_ROLLUP], constants: ConstantRollupData, @@ -107,7 +117,7 @@ impl BaseRollupInputs { let end_nullifier_tree_snapshot = self.check_nullifier_tree_non_membership_and_insert_to_tree(); // Validate public public data reads and public data update requests, and update public data tree - let end_public_data_tree_root = self.validate_and_process_public_state(); + let end_public_data_tree_snapshot = self.validate_and_process_public_state(); // Calculate the overall calldata hash let calldata_hash = BaseRollupInputs::components_compute_kernel_calldata_hash(self.kernel_data); @@ -128,8 +138,8 @@ impl BaseRollupInputs { end_nullifier_tree_snapshot : end_nullifier_tree_snapshot, start_contract_tree_snapshot : self.start_contract_tree_snapshot, end_contract_tree_snapshot : end_contract_tree_snapshot, - start_public_data_tree_root : self.start_public_data_tree_root, - end_public_data_tree_root : end_public_data_tree_root, + start_public_data_tree_snapshot : self.start_public_data_tree_snapshot, + end_public_data_tree_snapshot: end_public_data_tree_snapshot, calldata_hash : calldata_hash, } } @@ -240,7 +250,7 @@ impl BaseRollupInputs { calculate_subtree(leaves.map(|leaf:NullifierLeafPreimage| leaf.hash())) } - fn validate_and_process_public_state(self) -> Field { + fn validate_and_process_public_state(self) -> AppendOnlyTreeSnapshot { // TODO(#2521) - data read validation should happen against the current state of the tx and not the start state. // Blocks all interesting usecases that read and write to the same public state in the same tx. // https://aztecprotocol.slack.com/archives/C02M7VC7TN0/p1695809629015719?thread_ts=1695653252.007339&cid=C02M7VC7TN0 @@ -253,11 +263,19 @@ impl BaseRollupInputs { // 0, // self.new_public_data_reads_sibling_paths); - let mid_public_data_tree_root = insert_public_data_update_requests( - self.start_public_data_tree_root, - self.kernel_data[0].public_inputs.end.public_data_update_requests, - 0, - self.new_public_data_update_requests_sibling_paths + let mid_public_data_tree_snapshot = insert_public_data_update_requests( + self.start_public_data_tree_snapshot, + self.kernel_data[0].public_inputs.end.public_data_update_requests.map(|request: PublicDataUpdateRequest| { + PublicDataTreeLeaf { + slot: request.leaf_slot, + value: request.new_value, + } + }), + self.sorted_public_data_writes[0], + self.sorted_public_data_writes_indexes[0], + self.low_public_data_writes_preimages[0], + self.low_public_data_writes_witnesses[0], + self.public_data_writes_subtree_sibling_paths[0], ); @@ -274,14 +292,22 @@ impl BaseRollupInputs { // MAX_PUBLIC_DATA_READS_PER_TX, // baseRollupInputs.new_public_data_reads_sibling_paths); - let end_public_data_tree_root = insert_public_data_update_requests( - mid_public_data_tree_root, - self.kernel_data[1].public_inputs.end.public_data_update_requests, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - self.new_public_data_update_requests_sibling_paths + let end_public_data_tree_snapshot = insert_public_data_update_requests( + mid_public_data_tree_snapshot, + self.kernel_data[1].public_inputs.end.public_data_update_requests.map(|request: PublicDataUpdateRequest| { + PublicDataTreeLeaf { + slot: request.leaf_slot, + value: request.new_value, + } + }), + self.sorted_public_data_writes[1], + self.sorted_public_data_writes_indexes[1], + self.low_public_data_writes_preimages[1], + self.low_public_data_writes_witnesses[1], + self.public_data_writes_subtree_sibling_paths[1], ); - end_public_data_tree_root + end_public_data_tree_snapshot } // Computes the calldata hash for a base rollup @@ -321,7 +347,7 @@ impl BaseRollupInputs { for j in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { calldata_hash_inputs[offset + i * MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX * 2 + j * 2] = - public_data_update_requests[j].leaf_index; + public_data_update_requests[j].leaf_slot; calldata_hash_inputs[offset + i * MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX * 2 + j * 2 + 1] = public_data_update_requests[j].new_value; } @@ -413,41 +439,111 @@ fn verify_kernel_proof(proof: Proof) -> bool { } fn insert_public_data_update_requests( - tree_root: Field, - public_data_update_requests: [PublicDataUpdateRequest;MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - witnesses_offset: Field, - witnesses: [[Field; PUBLIC_DATA_TREE_HEIGHT]; 2 * MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] -) -> Field { - let mut root = tree_root; - - for i in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { - let state_write = public_data_update_requests[i]; - let witness = witnesses[i + witnesses_offset]; - - if (!state_write.is_empty()) { - components::assert_check_membership(state_write.old_value, state_write.leaf_index, witness, root); - root = components::root_from_sibling_path(state_write.new_value, state_write.leaf_index, witness); + prev_snapshot: AppendOnlyTreeSnapshot, + public_data_writes: [PublicDataTreeLeaf; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + sorted_public_data_writes: [PublicDataTreeLeaf; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + sorted_public_data_writes_indexes: [u32; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + low_public_data_writes_preimages: [PublicDataTreeLeafPreimage; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + low_public_data_writes_witnesses: [PublicDataMembershipWitness; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + public_data_writes_subtree_sibling_path: [Field; PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH] +) -> AppendOnlyTreeSnapshot { + crate::indexed_tree::batch_insert( + prev_snapshot, + public_data_writes, + sorted_public_data_writes, + sorted_public_data_writes_indexes, + public_data_writes_subtree_sibling_path, + low_public_data_writes_preimages, + low_public_data_writes_witnesses.map( + |witness: PublicDataMembershipWitness| { + MembershipWitness { + leaf_index: witness.leaf_index, + sibling_path: witness.sibling_path, + } } - } - - root + ), + |a: PublicDataTreeLeaf, b: PublicDataTreeLeaf| a.eq(b), // PublicDataTreeLeaf equals + |write: PublicDataTreeLeaf| write.is_empty(), // PublicDataTreeLeaf is_empty + |preimage: PublicDataTreeLeafPreimage| preimage.hash(), // Hash preimage + |low_preimage: PublicDataTreeLeafPreimage, write: PublicDataTreeLeaf| { // Is valid low preimage + let is_update = low_preimage.slot == write.slot; + let is_low_empty = low_preimage.is_empty(); + + let is_less_than_slot = full_field_less_than(low_preimage.slot, write.slot); + let is_next_greater_than = full_field_less_than(write.slot, low_preimage.next_slot); + let is_in_range = is_less_than_slot & ( + is_next_greater_than | + ((low_preimage.next_index == 0) & (low_preimage.next_slot == 0))); + + (!is_low_empty) & (is_update | is_in_range) + }, + |low_preimage: PublicDataTreeLeafPreimage, write: PublicDataTreeLeaf, write_index: u32| { // Update low leaf + let is_update = low_preimage.slot == write.slot; + if is_update { + PublicDataTreeLeafPreimage{ + slot : low_preimage.slot, + value: write.value, + next_slot : low_preimage.next_slot, + next_index : low_preimage.next_index, + } + } else { + PublicDataTreeLeafPreimage{ + slot : low_preimage.slot, + value: low_preimage.value, + next_slot : write.slot, + next_index : write_index, + } + } + }, + |write: PublicDataTreeLeaf, low_preimage: PublicDataTreeLeafPreimage| { // Build insertion leaf + let is_update = low_preimage.slot == write.slot; + if is_update { + PublicDataTreeLeafPreimage::default() + }else { + PublicDataTreeLeafPreimage { + slot: write.slot, + value: write.value, + next_slot : low_preimage.next_slot, + next_index : low_preimage.next_index, + } + } + }, + [0; PUBLIC_DATA_SUBTREE_HEIGHT], + [0; PUBLIC_DATA_TREE_HEIGHT] + ) } fn validate_public_data_reads( tree_root: Field, public_data_reads: [PublicDataRead; MAX_PUBLIC_DATA_READS_PER_TX], - witnesses_offset: Field, - witnesses: [[Field; PUBLIC_DATA_TREE_HEIGHT]; 2 * MAX_PUBLIC_DATA_READS_PER_TX] + public_data_reads_preimages: [PublicDataTreeLeafPreimage; MAX_PUBLIC_DATA_READS_PER_TX], + public_data_reads_witnesses: [PublicDataMembershipWitness; MAX_PUBLIC_DATA_READS_PER_TX] ) { for i in 0..MAX_PUBLIC_DATA_READS_PER_TX { - let public_data_read = public_data_reads[i]; - let witness = witnesses[i + witnesses_offset]; - - if (!public_data_read.is_empty()) { + let read = public_data_reads[i]; + let low_preimage = public_data_reads_preimages[i]; + let witness = public_data_reads_witnesses[i]; + + let is_low_empty = low_preimage.is_empty(); + let is_exact = low_preimage.slot == read.leaf_slot; + + let is_less_than_slot = full_field_less_than(low_preimage.slot, read.leaf_slot); + let is_next_greater_than = full_field_less_than(read.leaf_slot, low_preimage.next_slot); + let is_in_range = is_less_than_slot + & (is_next_greater_than | ((low_preimage.next_index == 0) & (low_preimage.next_slot == 0))); + + if (!read.is_empty()) { + assert(!is_low_empty, "public data read is not empty but low preimage is empty"); + if is_in_range { + assert_eq(read.value, 0, "low leaf for public data read is in range but value is not zero"); + } else { + assert(is_exact, "low leaf for public data read is invalid"); + assert_eq(read.value, low_preimage.value, "low leaf for public data has different value"); + } components::assert_check_membership( - public_data_read.value, - public_data_read.leaf_index, - witness, + low_preimage.hash(), + witness.leaf_index, + witness.sibling_path, tree_root ); } @@ -457,13 +553,17 @@ fn validate_public_data_reads( global NUM_CONTRACT_LEAVES = 2; #[test] fn consistent_num_contract_leaves() { - assert(NUM_CONTRACT_LEAVES == MAX_NEW_CONTRACTS_PER_TX * 2, "num contract leaves incorrect, see calculate_contract_leaves to see how it is computed"); + assert( + NUM_CONTRACT_LEAVES == MAX_NEW_CONTRACTS_PER_TX * 2, "num contract leaves incorrect, see calculate_contract_leaves to see how it is computed" + ); } global NOTE_HASH_SUBTREE_WIDTH = 128; #[test] fn consistent_not_hash_subtree_width() { - assert_eq(NOTE_HASH_SUBTREE_WIDTH, 2.pow_32(NOTE_HASH_SUBTREE_HEIGHT) as u32, "note hash subtree width is incorrect"); + assert_eq( + NOTE_HASH_SUBTREE_WIDTH, 2.pow_32(NOTE_HASH_SUBTREE_HEIGHT) as u32, "note hash subtree width is incorrect" + ); } global CALLDATA_HASH_INPUT_SIZE = 338; @@ -483,14 +583,18 @@ fn consistent_calldata_hash_input_size() { global CALL_DATA_HASH_LOG_FIELDS = 8; #[test] fn consistent_call_data_hash_log_fields() { - assert_eq(CALL_DATA_HASH_LOG_FIELDS, NUM_ENCRYPTED_LOGS_HASHES_PER_TX * NUM_FIELDS_PER_SHA256 * 2 - + NUM_UNENCRYPTED_LOGS_HASHES_PER_TX * NUM_FIELDS_PER_SHA256 * 2, "calldata hash log fields is incorrect"); + assert_eq( + CALL_DATA_HASH_LOG_FIELDS, NUM_ENCRYPTED_LOGS_HASHES_PER_TX * NUM_FIELDS_PER_SHA256 * 2 + + NUM_UNENCRYPTED_LOGS_HASHES_PER_TX * NUM_FIELDS_PER_SHA256 * 2, "calldata hash log fields is incorrect" + ); } global CALL_DATA_HASH_FULL_FIELDS = 330; #[test] fn consistent_call_data_hash_full_fields() { - assert_eq(CALL_DATA_HASH_FULL_FIELDS, CALLDATA_HASH_INPUT_SIZE - CALL_DATA_HASH_LOG_FIELDS, "calldata hash log fields is incorrect"); + assert_eq( + CALL_DATA_HASH_FULL_FIELDS, CALLDATA_HASH_INPUT_SIZE - CALL_DATA_HASH_LOG_FIELDS, "calldata hash log fields is incorrect" + ); } // TODO to radix returns u8, so we cannot use bigger radixes. It'd be ideal to use a radix of the maximum range-constrained integer noir supports @@ -534,6 +638,7 @@ mod tests { abis::append_only_tree_snapshot::AppendOnlyTreeSnapshot, abis::base_or_merge_rollup_public_inputs::BaseOrMergeRollupPublicInputs, abis::nullifier_leaf_preimage::NullifierLeafPreimage, + abis::public_data_tree_leaf::{PublicDataTreeLeafPreimage, PublicDataTreeLeaf}, abis::constant_rollup_data::ConstantRollupData, tests::merkle_tree_utils::{NonEmptyMerkleTree, compute_zero_hashes}, components, @@ -545,8 +650,8 @@ mod tests { ARCHIVE_HEIGHT, KERNELS_PER_BASE_ROLLUP, MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP, - MAX_PUBLIC_DATA_READS_PER_BASE_ROLLUP, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_BASE_ROLLUP, + MAX_PUBLIC_DATA_READS_PER_TX, + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, NOTE_HASH_TREE_HEIGHT, NOTE_HASH_SUBTREE_HEIGHT, @@ -554,11 +659,13 @@ mod tests { NULLIFIER_TREE_HEIGHT, NULLIFIER_SUBTREE_HEIGHT, PUBLIC_DATA_TREE_HEIGHT, + PUBLIC_DATA_SUBTREE_HEIGHT, + PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, NUM_FIELDS_PER_SHA256, }; use dep::types::{ abis::membership_witness::ArchiveRootMembershipWitness, - abis::membership_witness::NullifierMembershipWitness, + abis::membership_witness::{NullifierMembershipWitness, PublicDataMembershipWitness}, abis::new_contract_data::NewContractData, abis::public_data_read::PublicDataRead, abis::public_data_update_request::PublicDataUpdateRequest, @@ -575,23 +682,128 @@ mod tests { value: Field, } - - struct SortedNullifierTuple { - value: Field, + struct SortedTuple { + value: T, original_index: u32, } global MAX_NEW_NULLIFIERS_PER_TEST = 4; + global MAX_PUBLIC_DATA_WRITES_PER_TEST = 2; + global MAX_PUBLIC_DATA_READS_PER_TEST = 2; + + fn sort_high_to_low(values: [T; N], is_less_than: fn(T, T) -> bool) -> [SortedTuple; N] { + let mut sorted_tuples = [SortedTuple { value: values[0], original_index: 0 }; N]; + + for i in 0..N { + sorted_tuples[i] = SortedTuple { + value: values[i], + original_index: i as u32, + }; + } + + sorted_tuples.sort_via(|a: SortedTuple, b: SortedTuple| is_less_than(b.value, a.value)) + } + + fn update_public_data_tree_single_kernel( + public_data_tree: &mut NonEmptyMerkleTree, + kernel_data: &mut PreviousKernelData, + snapshot: AppendOnlyTreeSnapshot, + public_data_writes: BoundedVec<(u64, PublicDataTreeLeaf), 2>, + mut pre_existing_public_data: [PublicDataTreeLeafPreimage; EXISTING_LEAVES] + ) -> ( + [Field; PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH], + [PublicDataTreeLeaf; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + [u32; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + [PublicDataTreeLeafPreimage; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + [PublicDataMembershipWitness; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + [PublicDataTreeLeafPreimage; MAX_PUBLIC_DATA_READS_PER_TX], + [PublicDataMembershipWitness; MAX_PUBLIC_DATA_READS_PER_TX], + [PublicDataTreeLeafPreimage; EXISTING_LEAVES], + [PublicDataTreeLeafPreimage; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + ) { + let mut subtree_path: [Field; PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH] = dep::std::unsafe::zeroed(); + let mut sorted_public_data_writes: [PublicDataTreeLeaf; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] = dep::std::unsafe::zeroed(); + let mut sorted_public_data_writes_indexes: [u32; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] = dep::std::unsafe::zeroed(); + let mut low_public_data_writes_preimages: [PublicDataTreeLeafPreimage; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] = dep::std::unsafe::zeroed(); + let mut low_public_data_writes_witnesses: [PublicDataMembershipWitness; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] = dep::std::unsafe::zeroed(); + let mut public_data_reads_preimages: [PublicDataTreeLeafPreimage; MAX_PUBLIC_DATA_READS_PER_TX] = dep::std::unsafe::zeroed(); + let mut public_data_reads_witnesses: [PublicDataMembershipWitness; MAX_PUBLIC_DATA_READS_PER_TX] = dep::std::unsafe::zeroed(); + let mut new_subtree: [PublicDataTreeLeafPreimage; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] = dep::std::unsafe::zeroed(); + + for i in 0..MAX_PUBLIC_DATA_WRITES_PER_TEST { + if i < (public_data_writes.len() as u64) { + let (_, leaf): (u64, PublicDataTreeLeaf) = public_data_writes.get_unchecked(i as Field); + + kernel_data.public_inputs.end.public_data_update_requests[i] = PublicDataUpdateRequest { + leaf_slot : leaf.slot, + old_value : 0, + new_value : leaf.value, + }; + } + } + let mut sorted_write_tuples = sort_high_to_low( + public_data_writes.storage, + |(_, leaf_a): (u64, PublicDataTreeLeaf),(_,leaf_b):(u64, PublicDataTreeLeaf)| full_field_less_than(leaf_a.slot, leaf_b.slot) + ); + + for i in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { + if (i as u64) < MAX_PUBLIC_DATA_WRITES_PER_TEST { + let (low_leaf_index, leaf): (u64, PublicDataTreeLeaf) = sorted_write_tuples[i].value; + + sorted_public_data_writes[i] = leaf; + sorted_public_data_writes_indexes[i] = sorted_write_tuples[i].original_index; + + if !leaf.is_empty() { + let low_leaf = pre_existing_public_data[low_leaf_index]; + if low_leaf.slot == leaf.slot { + pre_existing_public_data[low_leaf_index].value = leaf.value; + } else { + new_subtree[sorted_write_tuples[i].original_index] = PublicDataTreeLeafPreimage { + slot: leaf.slot, + value: leaf.value, + next_slot: low_leaf.next_slot, + next_index: low_leaf.next_index, + }; + pre_existing_public_data[low_leaf_index] = PublicDataTreeLeafPreimage { + slot: low_leaf.slot, + value: low_leaf.value, + next_slot: leaf.slot, + next_index: (MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX as u32)+(i as u32) + }; + } + low_public_data_writes_preimages[i] = low_leaf; + low_public_data_writes_witnesses[i] = PublicDataMembershipWitness { + leaf_index: low_leaf_index as Field, + sibling_path: public_data_tree.get_sibling_path(low_leaf_index as Field) + }; + + public_data_tree.update_leaf( + low_leaf_index, + pre_existing_public_data[low_leaf_index].hash() + ); + } + } else { + sorted_public_data_writes[i] = PublicDataTreeLeaf::default(); + sorted_public_data_writes_indexes[i] = i as u32; + } + } + + subtree_path = BaseRollupInputsBuilder::extract_subtree_sibling_path(public_data_tree.get_sibling_path(snapshot.next_available_leaf_index as Field), [0; PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH]); + + ( + subtree_path, sorted_public_data_writes, sorted_public_data_writes_indexes, low_public_data_writes_preimages, low_public_data_writes_witnesses, public_data_reads_preimages, public_data_reads_witnesses, pre_existing_public_data, new_subtree + ) + } struct BaseRollupInputsBuilder { kernel_data: [PreviousKernelDataBuilder; KERNELS_PER_BASE_ROLLUP], pre_existing_notes: [Field; NOTE_HASH_SUBTREE_WIDTH], pre_existing_nullifiers: [NullifierLeafPreimage; MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP], pre_existing_contracts: [Field; NUM_CONTRACT_LEAVES], - pre_existing_public_data: [Field; MAX_PUBLIC_DATA_READS_PER_BASE_ROLLUP], + pre_existing_public_data: [PublicDataTreeLeafPreimage; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], pre_existing_blocks: [Field; KERNELS_PER_BASE_ROLLUP], - public_data_reads: BoundedVec, - public_data_writes: BoundedVec<(u64, Field), 2>, + public_data_reads: [BoundedVec; KERNELS_PER_BASE_ROLLUP], + public_data_writes: [BoundedVec<(u64, PublicDataTreeLeaf), MAX_PUBLIC_DATA_WRITES_PER_TEST>; KERNELS_PER_BASE_ROLLUP], new_nullifiers: BoundedVec, constants: ConstantRollupData, } @@ -641,19 +853,7 @@ mod tests { let mut low_nullifier_leaf_preimages: [NullifierLeafPreimage; MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP] = dep::std::unsafe::zeroed(); let mut low_nullifier_membership_witness: [NullifierMembershipWitness; MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP] = dep::std::unsafe::zeroed(); - let mut sorted_new_nullifier_tuples = [SortedNullifierTuple { - value: 0, - original_index: 0, - }; MAX_NEW_NULLIFIERS_PER_TEST]; - - - for i in 0..MAX_NEW_NULLIFIERS_PER_TEST { - sorted_new_nullifier_tuples[i] = SortedNullifierTuple { - value: self.new_nullifiers.get_unchecked(i).value, - original_index: i as u32, - }; - } - sorted_new_nullifier_tuples = sorted_new_nullifier_tuples.sort_via(|a: SortedNullifierTuple, b: SortedNullifierTuple| {full_field_less_than(b.value, a.value)}); + let sorted_new_nullifier_tuples = sort_high_to_low(self.new_nullifiers.storage.map(|insertion: NullifierInsertion| insertion.value), full_field_less_than); let mut sorted_new_nullifiers = [0; MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP]; let mut sorted_new_nullifiers_indexes = [0; MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP]; @@ -696,7 +896,121 @@ mod tests { } (low_nullifier_leaf_preimages, low_nullifier_membership_witness, sorted_new_nullifiers, sorted_new_nullifiers_indexes) - } + } + + fn update_public_data_tree_with_new_leaves( + mut self, + initial_public_data_tree: &mut NonEmptyMerkleTree, + kernel_data: &mut [PreviousKernelData; KERNELS_PER_BASE_ROLLUP], + start_public_data_tree_snapshot: AppendOnlyTreeSnapshot + ) -> ( + [[Field; PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH]; KERNELS_PER_BASE_ROLLUP], // Subtrees paths + [[PublicDataTreeLeaf; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; KERNELS_PER_BASE_ROLLUP], // sorted_public_data_writes + [[u32; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; KERNELS_PER_BASE_ROLLUP], // sorted_public_data_writes_indexes + [[PublicDataTreeLeafPreimage; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; KERNELS_PER_BASE_ROLLUP], // low_public_data_writes_preimages + [[PublicDataMembershipWitness; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; KERNELS_PER_BASE_ROLLUP], // low_public_data_writes_witnesses + [[PublicDataTreeLeafPreimage; MAX_PUBLIC_DATA_READS_PER_TX]; KERNELS_PER_BASE_ROLLUP], // public_data_reads_preimages + [[PublicDataMembershipWitness; MAX_PUBLIC_DATA_READS_PER_TX]; KERNELS_PER_BASE_ROLLUP], // public_data_reads_witnesses + ) { + let mut subtree_paths: [[Field; PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH]; KERNELS_PER_BASE_ROLLUP] = dep::std::unsafe::zeroed(); + let mut sorted_public_data_writes: [[PublicDataTreeLeaf; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; KERNELS_PER_BASE_ROLLUP] = dep::std::unsafe::zeroed(); + let mut sorted_public_data_writes_indexes: [[u32; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; KERNELS_PER_BASE_ROLLUP] = dep::std::unsafe::zeroed(); + let mut low_public_data_writes_preimages: [[PublicDataTreeLeafPreimage; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; KERNELS_PER_BASE_ROLLUP] = dep::std::unsafe::zeroed(); + let mut low_public_data_writes_witnesses: [[PublicDataMembershipWitness; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; KERNELS_PER_BASE_ROLLUP] = dep::std::unsafe::zeroed(); + let mut public_data_reads_preimages: [[PublicDataTreeLeafPreimage; MAX_PUBLIC_DATA_READS_PER_TX]; KERNELS_PER_BASE_ROLLUP] = dep::std::unsafe::zeroed(); + let mut public_data_reads_witnesses: [[PublicDataMembershipWitness; MAX_PUBLIC_DATA_READS_PER_TX]; KERNELS_PER_BASE_ROLLUP] = dep::std::unsafe::zeroed(); + + // First kernel + let mut first_kernel_data = kernel_data[0]; + let ( + first_kernel_subtree_path, + first_kernel_sorted_public_data_writes, + first_kernel_sorted_public_data_writes_indexes, + first_kernel_low_public_data_writes_preimages, + first_kernel_low_public_data_writes_witnesses, + first_kernel_public_data_reads_preimages, + first_kernel_public_data_reads_witnesses, + first_kernel_pre_existing_public_data, + first_kernel_new_subtree + ) = update_public_data_tree_single_kernel( + initial_public_data_tree, + &mut first_kernel_data, + start_public_data_tree_snapshot, + self.public_data_writes[0], + self.pre_existing_public_data, + ); + + subtree_paths[0] = first_kernel_subtree_path; + sorted_public_data_writes[0] = first_kernel_sorted_public_data_writes; + sorted_public_data_writes_indexes[0] = first_kernel_sorted_public_data_writes_indexes; + low_public_data_writes_preimages[0] = first_kernel_low_public_data_writes_preimages; + low_public_data_writes_witnesses[0] = first_kernel_low_public_data_writes_witnesses; + public_data_reads_preimages[0] = first_kernel_public_data_reads_preimages; + public_data_reads_witnesses[0] = first_kernel_public_data_reads_witnesses; + self.pre_existing_public_data = first_kernel_pre_existing_public_data; + kernel_data[0] = first_kernel_data; + + + // Second kernel + let mut middle_public_data = [PublicDataTreeLeafPreimage::default(); MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX*2]; + for i in 0..middle_public_data.len() { + if (i as u64) < (MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX as u64) { + middle_public_data[i] = self.pre_existing_public_data[i]; + }else{ + middle_public_data[i] = first_kernel_new_subtree[i-MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; + } + } + + let mut middle_public_data_tree = NonEmptyMerkleTree::new( + middle_public_data.map(|preimage: PublicDataTreeLeafPreimage| preimage.hash()), + [0; PUBLIC_DATA_TREE_HEIGHT], + [0; PUBLIC_DATA_TREE_HEIGHT - PUBLIC_DATA_SUBTREE_HEIGHT - 1], + [0; PUBLIC_DATA_SUBTREE_HEIGHT + 1] + ); + let middle_public_data_tree_snapshot = AppendOnlyTreeSnapshot { + root: middle_public_data_tree.get_root(), + next_available_leaf_index: middle_public_data_tree.get_next_available_index() as u32, + }; + + + let mut second_kernel_data = kernel_data[1]; + let ( + second_kernel_subtree_path, + second_kernel_sorted_public_data_writes, + second_kernel_sorted_public_data_writes_indexes, + second_kernel_low_public_data_writes_preimages, + second_kernel_low_public_data_writes_witnesses, + second_kernel_public_data_reads_preimages, + second_kernel_public_data_reads_witnesses, + second_kernel_pre_existing_public_data, + second_kernel_new_subtree + ) = update_public_data_tree_single_kernel( + &mut middle_public_data_tree, + &mut second_kernel_data, + middle_public_data_tree_snapshot, + self.public_data_writes[1], + middle_public_data, + ); + + subtree_paths[1] = second_kernel_subtree_path; + sorted_public_data_writes[1] = second_kernel_sorted_public_data_writes; + sorted_public_data_writes_indexes[1] = second_kernel_sorted_public_data_writes_indexes; + low_public_data_writes_preimages[1] = second_kernel_low_public_data_writes_preimages; + low_public_data_writes_witnesses[1] = second_kernel_low_public_data_writes_witnesses; + public_data_reads_preimages[1] = second_kernel_public_data_reads_preimages; + public_data_reads_witnesses[1] = second_kernel_public_data_reads_witnesses; + kernel_data[1] = second_kernel_data; + + ( + subtree_paths, + sorted_public_data_writes, + sorted_public_data_writes_indexes, + low_public_data_writes_preimages, + low_public_data_writes_witnesses, + public_data_reads_preimages, + public_data_reads_witnesses, + ) + } fn build_inputs(mut self) -> BaseRollupInputs { let mut kernel_data = self.kernel_data.map(|builder: PreviousKernelDataBuilder|{ @@ -729,8 +1043,16 @@ mod tests { }; let new_contracts_subtree_sibling_path = BaseRollupInputsBuilder::extract_subtree_sibling_path(start_contract_tree.get_sibling_path(self.pre_existing_contracts.len()), [0; CONTRACT_SUBTREE_SIBLING_PATH_LENGTH]); - let mut start_public_data_tree = NonEmptyMerkleTree::new(self.pre_existing_public_data, [0; PUBLIC_DATA_TREE_HEIGHT], [0; PUBLIC_DATA_TREE_HEIGHT - 5], [0; 5]); - let start_public_data_tree_root = start_public_data_tree.get_root(); + let mut start_public_data_tree = NonEmptyMerkleTree::new( + self.pre_existing_public_data.map(|preimage: PublicDataTreeLeafPreimage| preimage.hash()), + [0; PUBLIC_DATA_TREE_HEIGHT], + [0; PUBLIC_DATA_TREE_HEIGHT - PUBLIC_DATA_SUBTREE_HEIGHT], + [0; PUBLIC_DATA_SUBTREE_HEIGHT] + ); + let start_public_data_tree_snapshot = AppendOnlyTreeSnapshot { + root: start_public_data_tree.get_root(), + next_available_leaf_index: start_public_data_tree.get_next_available_index() as u32, + }; let start_archive = NonEmptyMerkleTree::new(self.pre_existing_blocks, [0; ARCHIVE_HEIGHT], [0; ARCHIVE_HEIGHT - 1], [0; 1]); let archive_snapshot = AppendOnlyTreeSnapshot { @@ -740,38 +1062,6 @@ mod tests { self.constants.archive_snapshot = archive_snapshot; - let mut new_public_data_reads_sibling_paths: [[Field; PUBLIC_DATA_TREE_HEIGHT]; MAX_PUBLIC_DATA_READS_PER_BASE_ROLLUP] = dep::std::unsafe::zeroed(); - - for i in 0..self.public_data_reads.max_len() { - if (i as u64) < (self.public_data_reads.len() as u64) { - let index = self.public_data_reads.get_unchecked(i); - let value = self.pre_existing_public_data[index]; - kernel_data[0].public_inputs.end.public_data_reads[i] = PublicDataRead { - leaf_index: index as Field, - value: value, - }; - new_public_data_reads_sibling_paths[i] = start_public_data_tree.get_sibling_path(index as Field); - } - } - - let mut new_public_data_update_requests_sibling_paths: [[Field; PUBLIC_DATA_TREE_HEIGHT]; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_BASE_ROLLUP] = dep::std::unsafe::zeroed(); - - for i in 0..self.public_data_writes.max_len() { - if (i as u64) < (self.public_data_writes.len() as u64) { - let write = self.public_data_writes.get_unchecked(i); - let index = write.0; - let new_value = write.1; - let old_value = self.pre_existing_public_data[index]; - kernel_data[0].public_inputs.end.public_data_update_requests[i] = PublicDataUpdateRequest { - leaf_index : index as Field, - old_value, - new_value, - }; - new_public_data_update_requests_sibling_paths[i] = start_public_data_tree.get_sibling_path(index as Field); - start_public_data_tree.update_leaf(index, new_value); - } - } - let ( low_nullifier_leaf_preimages, low_nullifier_membership_witness, @@ -781,12 +1071,23 @@ mod tests { let new_nullifiers_subtree_sibling_path = BaseRollupInputsBuilder::extract_subtree_sibling_path(start_nullifier_tree.get_sibling_path(self.pre_existing_nullifiers.len()), [0; NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH]); + let ( + public_data_writes_subtree_sibling_paths, + sorted_public_data_writes, + sorted_public_data_writes_indexes, + low_public_data_writes_preimages, + low_public_data_writes_witnesses, + public_data_reads_preimages, + public_data_reads_witnesses + ) = self.update_public_data_tree_with_new_leaves(&mut start_public_data_tree, &mut kernel_data, start_public_data_tree_snapshot); + + BaseRollupInputs { kernel_data: kernel_data, start_note_hash_tree_snapshot, start_nullifier_tree_snapshot, start_contract_tree_snapshot, - start_public_data_tree_root, + start_public_data_tree_snapshot, archive_snapshot, sorted_new_nullifiers, @@ -797,9 +1098,17 @@ mod tests { new_commitments_subtree_sibling_path, new_nullifiers_subtree_sibling_path, + public_data_writes_subtree_sibling_paths, new_contracts_subtree_sibling_path, - new_public_data_update_requests_sibling_paths, - new_public_data_reads_sibling_paths, + + sorted_public_data_writes, + sorted_public_data_writes_indexes, + low_public_data_writes_preimages, + low_public_data_writes_witnesses, + + public_data_reads_preimages, + public_data_reads_witnesses, + archive_root_membership_witnesses: [ ArchiveRootMembershipWitness { @@ -980,10 +1289,7 @@ mod tests { next_index : 0, }; - builder.new_nullifiers.push(NullifierInsertion { - existing_index: 0, - value: 1, - }); + builder.new_nullifiers.push(NullifierInsertion { existing_index: 0, value: 1 }); let mut tree_nullifiers = [NullifierLeafPreimage::default(); MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP * 2]; tree_nullifiers[0] = NullifierLeafPreimage { @@ -999,18 +1305,22 @@ mod tests { }; let mut end_nullifier_tree = NonEmptyMerkleTree::new( - tree_nullifiers.map(|preimage: NullifierLeafPreimage| preimage.hash()), - [0; NULLIFIER_TREE_HEIGHT], - [0; NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT - 1], + tree_nullifiers.map(|preimage: NullifierLeafPreimage| preimage.hash()), + [0; NULLIFIER_TREE_HEIGHT], + [0; NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT - 1], [0; NULLIFIER_SUBTREE_HEIGHT + 1] ); let output = builder.execute(); - assert(output.end_nullifier_tree_snapshot.eq(AppendOnlyTreeSnapshot { + assert( + output.end_nullifier_tree_snapshot.eq( + AppendOnlyTreeSnapshot { root: end_nullifier_tree.get_root(), - next_available_leaf_index: 2 * MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP as u32, - })); + next_available_leaf_index: 2 * MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP as u32 + } + ) + ); } #[test] @@ -1028,15 +1338,9 @@ mod tests { next_index : 0, }; - builder.new_nullifiers.push(NullifierInsertion { - existing_index: 1, - value: 8, - }); + builder.new_nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); for i in 1..builder.new_nullifiers.max_len() { - builder.new_nullifiers.push(NullifierInsertion { - existing_index: 1, - value: (8 + i) as Field, - }); + builder.new_nullifiers.push(NullifierInsertion { existing_index: 1, value: (8 + i) as Field }); } let output = builder.execute(); @@ -1066,16 +1370,20 @@ mod tests { }; let mut end_nullifier_tree = NonEmptyMerkleTree::new( - tree_nullifiers.map(|preimage: NullifierLeafPreimage| preimage.hash()), - [0; NULLIFIER_TREE_HEIGHT], - [0; NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT - 1], + tree_nullifiers.map(|preimage: NullifierLeafPreimage| preimage.hash()), + [0; NULLIFIER_TREE_HEIGHT], + [0; NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT - 1], [0; NULLIFIER_SUBTREE_HEIGHT + 1] ); - assert(output.end_nullifier_tree_snapshot.eq(AppendOnlyTreeSnapshot { + assert( + output.end_nullifier_tree_snapshot.eq( + AppendOnlyTreeSnapshot { root: end_nullifier_tree.get_root(), - next_available_leaf_index: 2 * MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP as u32, - })); + next_available_leaf_index: 2 * MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP as u32 + } + ) + ); } #[test(should_fail_with = "Invalid low leaf")] @@ -1093,14 +1401,8 @@ mod tests { next_index : 0, }; - builder.new_nullifiers.push(NullifierInsertion { - existing_index: 1, - value: 8, - }); - builder.new_nullifiers.push(NullifierInsertion { - existing_index: 1, - value: 8, - }); + builder.new_nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); + builder.new_nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); builder.fails(); } @@ -1120,14 +1422,8 @@ mod tests { next_index : 0, }; - builder.new_nullifiers.push(NullifierInsertion { - existing_index: 1, - value: 8, - }); - builder.new_nullifiers.push(NullifierInsertion { - existing_index: 1, - value: 8, - }); + builder.new_nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); + builder.new_nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); builder.fails(); } @@ -1186,8 +1482,16 @@ mod tests { unconstrained fn single_public_state_read() { let mut builder = BaseRollupInputsBuilder::new(); - builder.pre_existing_public_data[0] = 27; - builder.public_data_reads.push(0); + builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { + slot: 27, + value: 28, + next_slot: 0, + next_index: 0, + }; + + let mut first_kernel_reads = builder.public_data_reads[0]; + first_kernel_reads.push(0); + builder.public_data_reads[0] = first_kernel_reads; builder.succeeds(); } @@ -1196,44 +1500,119 @@ mod tests { unconstrained fn single_public_state_write() { let mut builder = BaseRollupInputsBuilder::new(); - builder.pre_existing_public_data[0] = 27; - builder.public_data_writes.push((0, 28)); + builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { + slot: 27, + value: 28, + next_slot: 0, + next_index: 0, + }; + + let mut first_kernel_writes = builder.public_data_writes[0]; + first_kernel_writes.push((0, PublicDataTreeLeaf { slot: 27, value: 29 })); + builder.public_data_writes[0] = first_kernel_writes; let outputs = builder.execute(); + let updated_leaf = PublicDataTreeLeafPreimage { slot: 27, value: 29, next_slot: 0, next_index: 0 }; + let mut expected_public_data_tree = NonEmptyMerkleTree::new( - [28, 0], + [updated_leaf.hash(), 0], [0; PUBLIC_DATA_TREE_HEIGHT], [0; PUBLIC_DATA_TREE_HEIGHT - 1], [0; 1] ); - assert_eq(outputs.end_public_data_tree_root, expected_public_data_tree.get_root()); + assert_eq(outputs.end_public_data_tree_snapshot.root, expected_public_data_tree.get_root()); } #[test] unconstrained fn multiple_public_state_read_writes() { let mut builder = BaseRollupInputsBuilder::new(); - builder.pre_existing_public_data[0] = 27; - builder.pre_existing_public_data[1] = 28; - builder.pre_existing_public_data[2] = 29; - builder.pre_existing_public_data[3] = 30; + builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { + slot: 20, + value: 40, + next_slot: 28, + next_index: 1, + }; + builder.pre_existing_public_data[1] = PublicDataTreeLeafPreimage { + slot: 28, + value: 41, + next_slot: 29, + next_index: 2, + }; + builder.pre_existing_public_data[2] = PublicDataTreeLeafPreimage { + slot: 29, + value: 42, + next_slot: 30, + next_index: 3, + }; + builder.pre_existing_public_data[3] = PublicDataTreeLeafPreimage { + slot: 30, + value: 43, + next_slot: 0, + next_index: 0, + }; + + let mut first_kernel_reads = builder.public_data_reads[0]; + let mut first_kernel_writes = builder.public_data_writes[0]; + + first_kernel_reads.push(0); + first_kernel_writes.push((0, PublicDataTreeLeaf { slot: 25, value: 60 })); - builder.public_data_reads.push(0); - builder.public_data_writes.push((0, 60)); - builder.public_data_writes.push((2, 61)); - builder.public_data_reads.push(3); + builder.public_data_reads[0] = first_kernel_reads; + builder.public_data_writes[0] = first_kernel_writes; + + let mut second_kernel_reads = builder.public_data_reads[1]; + let mut second_kernel_writes = builder.public_data_writes[1]; + + second_kernel_reads.push(4); + second_kernel_writes.push((0, PublicDataTreeLeaf { slot: 20, value: 90 })); + + builder.public_data_reads[1] = second_kernel_reads; + builder.public_data_writes[1] = second_kernel_writes; let outputs = builder.execute(); + let mut public_data_leaves = [0; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX * 2]; + public_data_leaves[0] = PublicDataTreeLeafPreimage { + slot: 20, + value: 90, + next_slot: 25, + next_index: MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX as u32, + }.hash(); + public_data_leaves[1] = PublicDataTreeLeafPreimage { + slot: 28, + value: 41, + next_slot: 29, + next_index: 2, + }.hash(); + public_data_leaves[2] = PublicDataTreeLeafPreimage { + slot: 29, + value: 42, + next_slot: 30, + next_index: 3, + }.hash(); + public_data_leaves[3] = PublicDataTreeLeafPreimage { + slot: 30, + value: 43, + next_slot: 0, + next_index: 0, + }.hash(); + public_data_leaves[MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] = PublicDataTreeLeafPreimage { + slot: 25, + value: 60, + next_slot: 28, + next_index: 1, + }.hash(); + let mut expected_public_data_tree = NonEmptyMerkleTree::new( - [60, 28, 61, 30], + public_data_leaves, [0; PUBLIC_DATA_TREE_HEIGHT], - [0; PUBLIC_DATA_TREE_HEIGHT - 2], - [0; 2] + [0; PUBLIC_DATA_TREE_HEIGHT - PUBLIC_DATA_SUBTREE_HEIGHT - 1], + [0; PUBLIC_DATA_SUBTREE_HEIGHT + 1] ); - assert_eq(outputs.end_public_data_tree_root, expected_public_data_tree.get_root()); + assert_eq(outputs.end_public_data_tree_snapshot.root, expected_public_data_tree.get_root()); } -} \ No newline at end of file +} diff --git a/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/components.nr b/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/components.nr index 2a67a1acd965..c6b68ea7718f 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/components.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/components.nr @@ -51,7 +51,7 @@ pub fn assert_prev_rollups_follow_on_from_each_other(left : BaseOrMergeRollupPub assert(left.end_note_hash_tree_snapshot.eq(right.start_note_hash_tree_snapshot), "input proofs have different note hash tree snapshots"); assert(left.end_nullifier_tree_snapshot.eq(right.start_nullifier_tree_snapshot),"input proofs have different nullifier tree snapshots"); assert(left.end_contract_tree_snapshot.eq(right.start_contract_tree_snapshot), "input proofs have different contract tree snapshots"); - assert(left.end_public_data_tree_root == right.start_public_data_tree_root, "input proofs have different public data tree snapshots"); + assert(left.end_public_data_tree_snapshot.eq(right.start_public_data_tree_snapshot), "input proofs have different public data tree snapshots"); } /** diff --git a/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/indexed_tree.nr b/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/indexed_tree.nr index 1f871270b2d7..8396921a23a9 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/indexed_tree.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/indexed_tree.nr @@ -86,7 +86,7 @@ pub fn batch_insert [PreviousRollupData; 2] { next_available_leaf_index: 2 }; - previous_rollup_data[0].base_or_merge_rollup_public_inputs.end_public_data_tree_root = 3; - previous_rollup_data[1].base_or_merge_rollup_public_inputs.start_public_data_tree_root = 3; + previous_rollup_data[0].base_or_merge_rollup_public_inputs.start_public_data_tree_snapshot = AppendOnlyTreeSnapshot { + root: 0, + next_available_leaf_index: 1 + }; + previous_rollup_data[0].base_or_merge_rollup_public_inputs.end_public_data_tree_snapshot =AppendOnlyTreeSnapshot { + root: 1, + next_available_leaf_index: 2 + }; + previous_rollup_data[1].base_or_merge_rollup_public_inputs.start_public_data_tree_snapshot = AppendOnlyTreeSnapshot { + root: 1, + next_available_leaf_index: 2 + }; + previous_rollup_data[1].base_or_merge_rollup_public_inputs.end_public_data_tree_snapshot =AppendOnlyTreeSnapshot { + root: 2, + next_available_leaf_index: 3 + }; previous_rollup_data[0].base_or_merge_rollup_public_inputs.rollup_type = BASE_ROLLUP_TYPE; previous_rollup_data[1].base_or_merge_rollup_public_inputs.rollup_type = BASE_ROLLUP_TYPE; diff --git a/yarn-project/noir-protocol-circuits/src/crates/types/src/abis/membership_witness.nr b/yarn-project/noir-protocol-circuits/src/crates/types/src/abis/membership_witness.nr index efead3d78a2b..d8a1c75d5838 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/types/src/abis/membership_witness.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/types/src/abis/membership_witness.nr @@ -6,6 +6,7 @@ use crate::constants::{ NOTE_HASH_TREE_HEIGHT, ROLLUP_VK_TREE_HEIGHT, ARCHIVE_HEIGHT, + PUBLIC_DATA_TREE_HEIGHT, }; struct MembershipWitness { @@ -37,6 +38,11 @@ struct NullifierMembershipWitness{ sibling_path: [Field; NULLIFIER_TREE_HEIGHT] } +struct PublicDataMembershipWitness{ + leaf_index: Field, + sibling_path: [Field; PUBLIC_DATA_TREE_HEIGHT] +} + struct ArchiveRootMembershipWitness{ leaf_index: Field, sibling_path: [Field; ARCHIVE_HEIGHT] diff --git a/yarn-project/noir-protocol-circuits/src/crates/types/src/abis/public_data_read.nr b/yarn-project/noir-protocol-circuits/src/crates/types/src/abis/public_data_read.nr index 0fefc9271685..8e3fc4bb497a 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/types/src/abis/public_data_read.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/types/src/abis/public_data_read.nr @@ -1,30 +1,30 @@ use crate::constants::GENERATOR_INDEX__PUBLIC_DATA_READ; struct PublicDataRead { - leaf_index : Field, + leaf_slot : Field, value : Field, } impl PublicDataRead { fn hash(self) -> Field { dep::std::hash::pedersen_hash_with_separator([ - self.leaf_index, + self.leaf_slot, self.value, ], GENERATOR_INDEX__PUBLIC_DATA_READ) } pub fn empty() -> Self { Self { - leaf_index : 0, + leaf_slot : 0, value : 0, } } pub fn is_empty(self) -> bool { - (self.leaf_index == 0) & (self.value == 0) + (self.leaf_slot == 0) & (self.value == 0) } pub fn eq(self, public_data_read: PublicDataRead) -> bool { - (public_data_read.leaf_index == self.leaf_index) & (public_data_read.value == self.value) + (public_data_read.leaf_slot == self.leaf_slot) & (public_data_read.value == self.value) } } diff --git a/yarn-project/noir-protocol-circuits/src/crates/types/src/abis/public_data_update_request.nr b/yarn-project/noir-protocol-circuits/src/crates/types/src/abis/public_data_update_request.nr index 2e6c51ccaa7a..798f2fcc35e8 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/types/src/abis/public_data_update_request.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/types/src/abis/public_data_update_request.nr @@ -1,7 +1,7 @@ use crate::constants::GENERATOR_INDEX__PUBLIC_DATA_UPDATE_REQUEST; struct PublicDataUpdateRequest { - leaf_index : Field, + leaf_slot : Field, old_value : Field, new_value : Field } @@ -9,26 +9,26 @@ struct PublicDataUpdateRequest { impl PublicDataUpdateRequest { pub fn empty() -> Self { Self { - leaf_index : 0, + leaf_slot : 0, old_value : 0, new_value : 0 } } pub fn eq(self, update_request: PublicDataUpdateRequest) -> bool { - (update_request.leaf_index == self.leaf_index) & (update_request.old_value == self.old_value) + (update_request.leaf_slot == self.leaf_slot) & (update_request.old_value == self.old_value) & (update_request.new_value == self.new_value) } pub fn hash(self) -> Field { dep::std::hash::pedersen_hash_with_separator([ - self.leaf_index, + self.leaf_slot, self.old_value, self.new_value ], GENERATOR_INDEX__PUBLIC_DATA_UPDATE_REQUEST) } pub fn is_empty(self) -> bool { - (self.leaf_index == 0) & (self.old_value == 0) & (self.new_value == 0) + (self.leaf_slot == 0) & (self.old_value == 0) & (self.new_value == 0) } } diff --git a/yarn-project/noir-protocol-circuits/src/crates/types/src/constants.nr b/yarn-project/noir-protocol-circuits/src/crates/types/src/constants.nr index f8a5c60bb62d..5f2115711cd3 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/types/src/constants.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/types/src/constants.nr @@ -62,7 +62,7 @@ global VK_TREE_HEIGHT: Field = 3; global FUNCTION_TREE_HEIGHT: Field = 5; global CONTRACT_TREE_HEIGHT: Field = 16; global NOTE_HASH_TREE_HEIGHT: Field = 32; -global PUBLIC_DATA_TREE_HEIGHT: Field = 254; +global PUBLIC_DATA_TREE_HEIGHT: Field = 40; global NULLIFIER_TREE_HEIGHT: Field = 20; global L1_TO_L2_MSG_TREE_HEIGHT: Field = 16; global ROLLUP_VK_TREE_HEIGHT: Field = 8; @@ -73,8 +73,10 @@ global CONTRACT_SUBTREE_SIBLING_PATH_LENGTH: Field = 15; global NOTE_HASH_SUBTREE_HEIGHT: Field = 7; global NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH: Field = 25; global NULLIFIER_SUBTREE_HEIGHT: Field = 7; +global PUBLIC_DATA_SUBTREE_HEIGHT: Field = 4; global ARCHIVE_HEIGHT: Field = 16; global NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH: Field = 13; +global PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH: Field = 36; global L1_TO_L2_MSG_SUBTREE_HEIGHT: Field = 4; global L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH: Field = 12; diff --git a/yarn-project/noir-protocol-circuits/src/crates/types/src/tests/previous_kernel_data_builder.nr b/yarn-project/noir-protocol-circuits/src/crates/types/src/tests/previous_kernel_data_builder.nr index cdb5d867f967..e8d0d2eb8177 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/types/src/tests/previous_kernel_data_builder.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/types/src/tests/previous_kernel_data_builder.nr @@ -79,7 +79,7 @@ impl PreviousKernelDataBuilder { if i as u64 < num_updates as u64 { let update_request = PublicDataUpdateRequest { // The default leaf index is its index + 23. - leaf_index: value_offset + i + 23, + leaf_slot: value_offset + i + 23, // The default value is its index + 45. old_value: value_offset + i + 45, // The default value is its index + 678. @@ -96,7 +96,7 @@ impl PreviousKernelDataBuilder { if i as u64 < num_reads as u64 { let read_request = PublicDataRead { // The default leaf index is its index + 34. - leaf_index: value_offset + i + 34, + leaf_slot: value_offset + i + 34, // The default value is its index + 5566. value: value_offset + i + 5566, }; diff --git a/yarn-project/noir-protocol-circuits/src/scripts/generate_ts_from_abi.ts b/yarn-project/noir-protocol-circuits/src/scripts/generate_ts_from_abi.ts index f189d5926886..2eb6fcf4cc40 100644 --- a/yarn-project/noir-protocol-circuits/src/scripts/generate_ts_from_abi.ts +++ b/yarn-project/noir-protocol-circuits/src/scripts/generate_ts_from_abi.ts @@ -111,9 +111,13 @@ function generateStructInterfaces(type: ABIType, output: Set): string { let result = ''; // Edge case to handle the array of structs case. - if (type.kind === 'array' && type.type.kind === 'struct' && !output.has(getLastComponentOfPath(type.type.path))) { + if ( + type.kind === 'array' && + ((type.type.kind === 'struct' && !output.has(getLastComponentOfPath(type.type.path))) || type.type.kind === 'array') + ) { result += generateStructInterfaces(type.type, output); } + if (type.kind !== 'struct') { return result; } diff --git a/yarn-project/noir-protocol-circuits/src/type_conversion.ts b/yarn-project/noir-protocol-circuits/src/type_conversion.ts index 986d7a9817c1..22e6d7d01631 100644 --- a/yarn-project/noir-protocol-circuits/src/type_conversion.ts +++ b/yarn-project/noir-protocol-circuits/src/type_conversion.ts @@ -43,6 +43,7 @@ import { NewContractData, NullifierLeafPreimage, OptionallyRevealedData, + PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, PUBLIC_DATA_TREE_HEIGHT, Point, PreviousKernelData, @@ -57,6 +58,8 @@ import { PublicCallStackItem, PublicCircuitPublicInputs, PublicDataRead, + PublicDataTreeLeaf, + PublicDataTreeLeafPreimage, PublicDataUpdateRequest, PublicKernelInputs, ReadRequestMembershipWitness, @@ -118,6 +121,9 @@ import { BaseRollupInputs as BaseRollupInputsNoir, NullifierLeafPreimage as NullifierLeafPreimageNoir, NullifierMembershipWitness as NullifierMembershipWitnessNoir, + PublicDataMembershipWitness as PublicDataMembershipWitnessNoir, + PublicDataTreeLeaf as PublicDataTreeLeafNoir, + PublicDataTreeLeafPreimage as PublicDataTreeLeafPreimageNoir, } from './types/rollup_base_types.js'; import { MergeRollupInputs as MergeRollupInputsNoir } from './types/rollup_merge_types.js'; import { @@ -681,7 +687,7 @@ export function mapPublicDataUpdateRequestFromNoir( publicDataUpdateRequest: PublicDataUpdateRequestNoir, ): PublicDataUpdateRequest { return new PublicDataUpdateRequest( - mapFieldFromNoir(publicDataUpdateRequest.leaf_index), + mapFieldFromNoir(publicDataUpdateRequest.leaf_slot), mapFieldFromNoir(publicDataUpdateRequest.old_value), mapFieldFromNoir(publicDataUpdateRequest.new_value), ); @@ -696,7 +702,7 @@ export function mapPublicDataUpdateRequestToNoir( publicDataUpdateRequest: PublicDataUpdateRequest, ): PublicDataUpdateRequestNoir { return { - leaf_index: mapFieldToNoir(publicDataUpdateRequest.leafIndex), + leaf_slot: mapFieldToNoir(publicDataUpdateRequest.leafSlot), old_value: mapFieldToNoir(publicDataUpdateRequest.oldValue), new_value: mapFieldToNoir(publicDataUpdateRequest.newValue), }; @@ -708,7 +714,7 @@ export function mapPublicDataUpdateRequestToNoir( * @returns The parsed public data read. */ export function mapPublicDataReadFromNoir(publicDataRead: PublicDataReadNoir): PublicDataRead { - return new PublicDataRead(mapFieldFromNoir(publicDataRead.leaf_index), mapFieldFromNoir(publicDataRead.value)); + return new PublicDataRead(mapFieldFromNoir(publicDataRead.leaf_slot), mapFieldFromNoir(publicDataRead.value)); } /** @@ -718,7 +724,7 @@ export function mapPublicDataReadFromNoir(publicDataRead: PublicDataReadNoir): P */ export function mapPublicDataReadToNoir(publicDataRead: PublicDataRead): PublicDataReadNoir { return { - leaf_index: mapFieldToNoir(publicDataRead.leafIndex), + leaf_slot: mapFieldToNoir(publicDataRead.leafSlot), value: mapFieldToNoir(publicDataRead.value), }; } @@ -1108,8 +1114,12 @@ export function mapBaseOrMergeRollupPublicInputsToNoir( baseOrMergeRollupPublicInputs.startContractTreeSnapshot, ), end_contract_tree_snapshot: mapAppendOnlyTreeSnapshotToNoir(baseOrMergeRollupPublicInputs.endContractTreeSnapshot), - start_public_data_tree_root: mapFieldToNoir(baseOrMergeRollupPublicInputs.startPublicDataTreeRoot), - end_public_data_tree_root: mapFieldToNoir(baseOrMergeRollupPublicInputs.endPublicDataTreeRoot), + start_public_data_tree_snapshot: mapAppendOnlyTreeSnapshotToNoir( + baseOrMergeRollupPublicInputs.startPublicDataTreeSnapshot, + ), + end_public_data_tree_snapshot: mapAppendOnlyTreeSnapshotToNoir( + baseOrMergeRollupPublicInputs.endPublicDataTreeSnapshot, + ), calldata_hash: mapTuple(baseOrMergeRollupPublicInputs.calldataHash, mapFieldToNoir), }; } @@ -1161,8 +1171,8 @@ export function mapBaseOrMergeRollupPublicInputsFromNoir( mapAppendOnlyTreeSnapshotFromNoir(baseOrMergeRollupPublicInputs.end_nullifier_tree_snapshot), mapAppendOnlyTreeSnapshotFromNoir(baseOrMergeRollupPublicInputs.start_contract_tree_snapshot), mapAppendOnlyTreeSnapshotFromNoir(baseOrMergeRollupPublicInputs.end_contract_tree_snapshot), - mapFieldFromNoir(baseOrMergeRollupPublicInputs.start_public_data_tree_root), - mapFieldFromNoir(baseOrMergeRollupPublicInputs.end_public_data_tree_root), + mapAppendOnlyTreeSnapshotFromNoir(baseOrMergeRollupPublicInputs.start_public_data_tree_snapshot), + mapAppendOnlyTreeSnapshotFromNoir(baseOrMergeRollupPublicInputs.end_public_data_tree_snapshot), mapTupleFromNoir(baseOrMergeRollupPublicInputs.calldata_hash, 2, mapFieldFromNoir), ); } @@ -1260,8 +1270,8 @@ export function mapRootRollupPublicInputsFromNoir( mapAppendOnlyTreeSnapshotFromNoir(rootRollupPublicInputs.end_nullifier_tree_snapshot), mapAppendOnlyTreeSnapshotFromNoir(rootRollupPublicInputs.start_contract_tree_snapshot), mapAppendOnlyTreeSnapshotFromNoir(rootRollupPublicInputs.end_contract_tree_snapshot), - mapFieldFromNoir(rootRollupPublicInputs.start_public_data_tree_root), - mapFieldFromNoir(rootRollupPublicInputs.end_public_data_tree_root), + mapAppendOnlyTreeSnapshotFromNoir(rootRollupPublicInputs.start_public_data_tree_snapshot), + mapAppendOnlyTreeSnapshotFromNoir(rootRollupPublicInputs.end_public_data_tree_snapshot), mapAppendOnlyTreeSnapshotFromNoir(rootRollupPublicInputs.start_l1_to_l2_messages_tree_snapshot), mapAppendOnlyTreeSnapshotFromNoir(rootRollupPublicInputs.end_l1_to_l2_messages_tree_snapshot), mapAppendOnlyTreeSnapshotFromNoir(rootRollupPublicInputs.start_archive_snapshot), @@ -1311,6 +1321,18 @@ export function mapNullifierMembershipWitnessToNoir( }; } +/** + * Maps a membership witness of the public data tree to noir. + */ +export function mapPublicDataMembershipWitnessToNoir( + membershipWitness: MembershipWitness, +): PublicDataMembershipWitnessNoir { + return { + leaf_index: membershipWitness.leafIndex.toString(), + sibling_path: mapTuple(membershipWitness.siblingPath, mapFieldToNoir), + }; +} + /** * Maps a membership witness of the blocks tree to noir. * @param membershipWitness - The membership witness. @@ -1325,6 +1347,28 @@ export function mapArchiveRootMembershipWitnessToNoir( }; } +/** + * Maps a leaf of the public data tree to noir. + */ +export function mapPublicDataTreeLeafToNoir(leaf: PublicDataTreeLeaf): PublicDataTreeLeafNoir { + return { + slot: mapFieldToNoir(leaf.slot), + value: mapFieldToNoir(leaf.value), + }; +} + +/** + * Maps a leaf preimage of the public data tree to noir. + */ +export function mapPublicDataTreePreimageToNoir(preimage: PublicDataTreeLeafPreimage): PublicDataTreeLeafPreimageNoir { + return { + slot: mapFieldToNoir(preimage.slot), + value: mapFieldToNoir(preimage.value), + next_slot: mapFieldToNoir(preimage.nextSlot), + next_index: mapNumberToNoir(Number(preimage.nextIndex)), + }; +} + /** * Maps the inputs to the base rollup to noir. * @param input - The circuits.js base rollup inputs. @@ -1336,10 +1380,12 @@ export function mapBaseRollupInputsToNoir(inputs: BaseRollupInputs): BaseRollupI start_note_hash_tree_snapshot: mapAppendOnlyTreeSnapshotToNoir(inputs.startNoteHashTreeSnapshot), start_nullifier_tree_snapshot: mapAppendOnlyTreeSnapshotToNoir(inputs.startNullifierTreeSnapshot), start_contract_tree_snapshot: mapAppendOnlyTreeSnapshotToNoir(inputs.startContractTreeSnapshot), - start_public_data_tree_root: mapFieldToNoir(inputs.startPublicDataTreeRoot), + start_public_data_tree_snapshot: mapAppendOnlyTreeSnapshotToNoir(inputs.startPublicDataTreeSnapshot), archive_snapshot: mapAppendOnlyTreeSnapshotToNoir(inputs.archiveSnapshot), sorted_new_nullifiers: mapTuple(inputs.sortedNewNullifiers, mapFieldToNoir), - sorted_new_nullifiers_indexes: mapTuple(inputs.sortednewNullifiersIndexes, mapNumberToNoir), + sorted_new_nullifiers_indexes: mapTuple(inputs.sortednewNullifiersIndexes, (index: number) => + mapNumberToNoir(index), + ), low_nullifier_leaf_preimages: mapTuple(inputs.lowNullifierLeafPreimages, mapNullifierLeafPreimageToNoir), low_nullifier_membership_witness: mapTuple( inputs.lowNullifierMembershipWitness, @@ -1347,15 +1393,63 @@ export function mapBaseRollupInputsToNoir(inputs: BaseRollupInputs): BaseRollupI ), new_commitments_subtree_sibling_path: mapTuple(inputs.newCommitmentsSubtreeSiblingPath, mapFieldToNoir), new_nullifiers_subtree_sibling_path: mapTuple(inputs.newNullifiersSubtreeSiblingPath, mapFieldToNoir), + public_data_writes_subtree_sibling_paths: mapTuple( + inputs.publicDataWritesSubtreeSiblingPaths, + (txPath: Tuple) => { + return mapTuple(txPath, mapFieldToNoir); + }, + ), new_contracts_subtree_sibling_path: mapTuple(inputs.newContractsSubtreeSiblingPath, mapFieldToNoir), - new_public_data_update_requests_sibling_paths: mapTuple( - inputs.newPublicDataUpdateRequestsSiblingPaths, - (siblingPath: Tuple) => mapTuple(siblingPath, mapFieldToNoir), + + sorted_public_data_writes: mapTuple( + inputs.sortedPublicDataWrites, + (kernelWrites: Tuple) => { + return mapTuple(kernelWrites, mapPublicDataTreeLeafToNoir); + }, ), - new_public_data_reads_sibling_paths: mapTuple( - inputs.newPublicDataReadsSiblingPaths, - (siblingPath: Tuple) => mapTuple(siblingPath, mapFieldToNoir), + + sorted_public_data_writes_indexes: mapTuple( + inputs.sortedPublicDataWritesIndexes, + (txIndexes: Tuple) => { + return mapTuple(txIndexes, (index: number) => mapNumberToNoir(index)); + }, ), + + low_public_data_writes_preimages: mapTuple( + inputs.lowPublicDataWritesPreimages, + (txPreimages: Tuple) => { + return mapTuple(txPreimages, mapPublicDataTreePreimageToNoir); + }, + ), + + low_public_data_writes_witnesses: mapTuple( + inputs.lowPublicDataWritesMembershipWitnesses, + ( + txWitnesses: Tuple< + MembershipWitness, + typeof MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + >, + ) => { + return mapTuple(txWitnesses, mapPublicDataMembershipWitnessToNoir); + }, + ), + + public_data_reads_preimages: mapTuple( + inputs.publicDataReadsPreimages, + (txReadsPreimages: Tuple) => { + return mapTuple(txReadsPreimages, mapPublicDataTreePreimageToNoir); + }, + ), + + public_data_reads_witnesses: mapTuple( + inputs.publicDataReadsMembershipWitnesses, + ( + txReadsWitnesses: Tuple, typeof MAX_PUBLIC_DATA_READS_PER_TX>, + ) => { + return mapTuple(txReadsWitnesses, mapPublicDataMembershipWitnessToNoir); + }, + ), + archive_root_membership_witnesses: mapTuple( inputs.archiveRootMembershipWitnesses, mapArchiveRootMembershipWitnessToNoir, diff --git a/yarn-project/noir-protocol-circuits/src/types/private_kernel_init_types.ts b/yarn-project/noir-protocol-circuits/src/types/private_kernel_init_types.ts index fc97eb92e9ed..98feb594bacc 100644 --- a/yarn-project/noir-protocol-circuits/src/types/private_kernel_init_types.ts +++ b/yarn-project/noir-protocol-circuits/src/types/private_kernel_init_types.ts @@ -174,13 +174,13 @@ export interface OptionallyRevealedData { } export interface PublicDataUpdateRequest { - leaf_index: Field; + leaf_slot: Field; old_value: Field; new_value: Field; } export interface PublicDataRead { - leaf_index: Field; + leaf_slot: Field; value: Field; } diff --git a/yarn-project/noir-protocol-circuits/src/types/private_kernel_inner_types.ts b/yarn-project/noir-protocol-circuits/src/types/private_kernel_inner_types.ts index 65ef8c19fab0..c91bdd6dfaa5 100644 --- a/yarn-project/noir-protocol-circuits/src/types/private_kernel_inner_types.ts +++ b/yarn-project/noir-protocol-circuits/src/types/private_kernel_inner_types.ts @@ -57,13 +57,13 @@ export interface OptionallyRevealedData { } export interface PublicDataUpdateRequest { - leaf_index: Field; + leaf_slot: Field; old_value: Field; new_value: Field; } export interface PublicDataRead { - leaf_index: Field; + leaf_slot: Field; value: Field; } diff --git a/yarn-project/noir-protocol-circuits/src/types/private_kernel_ordering_types.ts b/yarn-project/noir-protocol-circuits/src/types/private_kernel_ordering_types.ts index b59356818fa9..12912563f60a 100644 --- a/yarn-project/noir-protocol-circuits/src/types/private_kernel_ordering_types.ts +++ b/yarn-project/noir-protocol-circuits/src/types/private_kernel_ordering_types.ts @@ -57,13 +57,13 @@ export interface OptionallyRevealedData { } export interface PublicDataUpdateRequest { - leaf_index: Field; + leaf_slot: Field; old_value: Field; new_value: Field; } export interface PublicDataRead { - leaf_index: Field; + leaf_slot: Field; value: Field; } diff --git a/yarn-project/noir-protocol-circuits/src/types/public_kernel_private_previous_types.ts b/yarn-project/noir-protocol-circuits/src/types/public_kernel_private_previous_types.ts index f0ba294ede20..03c45e6e7b1e 100644 --- a/yarn-project/noir-protocol-circuits/src/types/public_kernel_private_previous_types.ts +++ b/yarn-project/noir-protocol-circuits/src/types/public_kernel_private_previous_types.ts @@ -57,13 +57,13 @@ export interface OptionallyRevealedData { } export interface PublicDataUpdateRequest { - leaf_index: Field; + leaf_slot: Field; old_value: Field; new_value: Field; } export interface PublicDataRead { - leaf_index: Field; + leaf_slot: Field; value: Field; } diff --git a/yarn-project/noir-protocol-circuits/src/types/public_kernel_public_previous_types.ts b/yarn-project/noir-protocol-circuits/src/types/public_kernel_public_previous_types.ts index 11118ebb0b34..83e7bb12517f 100644 --- a/yarn-project/noir-protocol-circuits/src/types/public_kernel_public_previous_types.ts +++ b/yarn-project/noir-protocol-circuits/src/types/public_kernel_public_previous_types.ts @@ -57,13 +57,13 @@ export interface OptionallyRevealedData { } export interface PublicDataUpdateRequest { - leaf_index: Field; + leaf_slot: Field; old_value: Field; new_value: Field; } export interface PublicDataRead { - leaf_index: Field; + leaf_slot: Field; value: Field; } diff --git a/yarn-project/noir-protocol-circuits/src/types/rollup_base_types.ts b/yarn-project/noir-protocol-circuits/src/types/rollup_base_types.ts index 4ffad0677f64..d085b8f424ce 100644 --- a/yarn-project/noir-protocol-circuits/src/types/rollup_base_types.ts +++ b/yarn-project/noir-protocol-circuits/src/types/rollup_base_types.ts @@ -57,13 +57,13 @@ export interface OptionallyRevealedData { } export interface PublicDataUpdateRequest { - leaf_index: Field; + leaf_slot: Field; old_value: Field; new_value: Field; } export interface PublicDataRead { - leaf_index: Field; + leaf_slot: Field; value: Field; } @@ -158,6 +158,23 @@ export interface NullifierMembershipWitness { sibling_path: FixedLengthArray; } +export interface PublicDataTreeLeaf { + slot: Field; + value: Field; +} + +export interface PublicDataTreeLeafPreimage { + slot: Field; + value: Field; + next_slot: Field; + next_index: u32; +} + +export interface PublicDataMembershipWitness { + leaf_index: Field; + sibling_path: FixedLengthArray; +} + export interface ArchiveRootMembershipWitness { leaf_index: Field; sibling_path: FixedLengthArray; @@ -184,7 +201,7 @@ export interface BaseRollupInputs { start_note_hash_tree_snapshot: AppendOnlyTreeSnapshot; start_nullifier_tree_snapshot: AppendOnlyTreeSnapshot; start_contract_tree_snapshot: AppendOnlyTreeSnapshot; - start_public_data_tree_root: Field; + start_public_data_tree_snapshot: AppendOnlyTreeSnapshot; archive_snapshot: AppendOnlyTreeSnapshot; sorted_new_nullifiers: FixedLengthArray; sorted_new_nullifiers_indexes: FixedLengthArray; @@ -192,9 +209,14 @@ export interface BaseRollupInputs { low_nullifier_membership_witness: FixedLengthArray; new_commitments_subtree_sibling_path: FixedLengthArray; new_nullifiers_subtree_sibling_path: FixedLengthArray; + public_data_writes_subtree_sibling_paths: FixedLengthArray, 2>; new_contracts_subtree_sibling_path: FixedLengthArray; - new_public_data_update_requests_sibling_paths: FixedLengthArray, 32>; - new_public_data_reads_sibling_paths: FixedLengthArray, 32>; + sorted_public_data_writes: FixedLengthArray, 2>; + sorted_public_data_writes_indexes: FixedLengthArray, 2>; + low_public_data_writes_preimages: FixedLengthArray, 2>; + low_public_data_writes_witnesses: FixedLengthArray, 2>; + public_data_reads_preimages: FixedLengthArray, 2>; + public_data_reads_witnesses: FixedLengthArray, 2>; archive_root_membership_witnesses: FixedLengthArray; constants: ConstantRollupData; } @@ -210,8 +232,8 @@ export interface BaseOrMergeRollupPublicInputs { end_nullifier_tree_snapshot: AppendOnlyTreeSnapshot; start_contract_tree_snapshot: AppendOnlyTreeSnapshot; end_contract_tree_snapshot: AppendOnlyTreeSnapshot; - start_public_data_tree_root: Field; - end_public_data_tree_root: Field; + start_public_data_tree_snapshot: AppendOnlyTreeSnapshot; + end_public_data_tree_snapshot: AppendOnlyTreeSnapshot; calldata_hash: FixedLengthArray; } diff --git a/yarn-project/noir-protocol-circuits/src/types/rollup_merge_types.ts b/yarn-project/noir-protocol-circuits/src/types/rollup_merge_types.ts index e6a4f760ab76..70023af42668 100644 --- a/yarn-project/noir-protocol-circuits/src/types/rollup_merge_types.ts +++ b/yarn-project/noir-protocol-circuits/src/types/rollup_merge_types.ts @@ -41,8 +41,8 @@ export interface BaseOrMergeRollupPublicInputs { end_nullifier_tree_snapshot: AppendOnlyTreeSnapshot; start_contract_tree_snapshot: AppendOnlyTreeSnapshot; end_contract_tree_snapshot: AppendOnlyTreeSnapshot; - start_public_data_tree_root: Field; - end_public_data_tree_root: Field; + start_public_data_tree_snapshot: AppendOnlyTreeSnapshot; + end_public_data_tree_snapshot: AppendOnlyTreeSnapshot; calldata_hash: FixedLengthArray; } diff --git a/yarn-project/noir-protocol-circuits/src/types/rollup_root_types.ts b/yarn-project/noir-protocol-circuits/src/types/rollup_root_types.ts index b1ad54abde9b..550b57bc13e3 100644 --- a/yarn-project/noir-protocol-circuits/src/types/rollup_root_types.ts +++ b/yarn-project/noir-protocol-circuits/src/types/rollup_root_types.ts @@ -41,8 +41,8 @@ export interface BaseOrMergeRollupPublicInputs { end_nullifier_tree_snapshot: AppendOnlyTreeSnapshot; start_contract_tree_snapshot: AppendOnlyTreeSnapshot; end_contract_tree_snapshot: AppendOnlyTreeSnapshot; - start_public_data_tree_root: Field; - end_public_data_tree_root: Field; + start_public_data_tree_snapshot: AppendOnlyTreeSnapshot; + end_public_data_tree_snapshot: AppendOnlyTreeSnapshot; calldata_hash: FixedLengthArray; } @@ -81,8 +81,8 @@ export interface RootRollupPublicInputs { end_nullifier_tree_snapshot: AppendOnlyTreeSnapshot; start_contract_tree_snapshot: AppendOnlyTreeSnapshot; end_contract_tree_snapshot: AppendOnlyTreeSnapshot; - start_public_data_tree_root: Field; - end_public_data_tree_root: Field; + start_public_data_tree_snapshot: AppendOnlyTreeSnapshot; + end_public_data_tree_snapshot: AppendOnlyTreeSnapshot; start_l1_to_l2_messages_tree_snapshot: AppendOnlyTreeSnapshot; end_l1_to_l2_messages_tree_snapshot: AppendOnlyTreeSnapshot; start_archive_snapshot: AppendOnlyTreeSnapshot; diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 4b3e21aee649..537d60b654ff 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -10,7 +10,14 @@ import { PublicKey, } from '@aztec/circuits.js'; import { createDebugLogger } from '@aztec/foundation/log'; -import { KeyStore, L2Block, MerkleTreeId, NullifierMembershipWitness, StateInfoProvider } from '@aztec/types'; +import { + KeyStore, + L2Block, + MerkleTreeId, + NullifierMembershipWitness, + PublicDataWitness, + StateInfoProvider, +} from '@aztec/types'; import { ContractDataOracle } from '../contract_data_oracle/index.js'; import { PxeDatabase } from '../database/index.js'; @@ -174,6 +181,10 @@ export class SimulatorOracle implements DBOracle { return await this.stateInfoProvider.getBlock(blockNumber); } + public async getPublicDataTreeWitness(blockNumber: number, leafSlot: Fr): Promise { + return await this.stateInfoProvider.getPublicDataTreeWitness(blockNumber, leafSlot); + } + /** * Retrieve the databases view of the Block Header object. * This structure is fed into the circuits simulator and is used to prove against certain historical roots. diff --git a/yarn-project/pxe/src/synchronizer/synchronizer.ts b/yarn-project/pxe/src/synchronizer/synchronizer.ts index 2346ae7d613d..76663ea34fb1 100644 --- a/yarn-project/pxe/src/synchronizer/synchronizer.ts +++ b/yarn-project/pxe/src/synchronizer/synchronizer.ts @@ -208,7 +208,7 @@ export class Synchronizer { block.endL1ToL2MessagesTreeSnapshot.root, block.endArchiveSnapshot.root, Fr.ZERO, // todo: private kernel vk tree root - block.endPublicDataTreeRoot, + block.endPublicDataTreeSnapshot.root, globalsHash, ); diff --git a/yarn-project/sequencer-client/src/block_builder/solo_block_builder.test.ts b/yarn-project/sequencer-client/src/block_builder/solo_block_builder.test.ts index 5909afce8b3e..06c550689d75 100644 --- a/yarn-project/sequencer-client/src/block_builder/solo_block_builder.test.ts +++ b/yarn-project/sequencer-client/src/block_builder/solo_block_builder.test.ts @@ -11,7 +11,9 @@ import { MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NULLIFIER_SUBTREE_HEIGHT, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, + PUBLIC_DATA_SUBTREE_HEIGHT, Proof, + PublicDataTreeLeaf, PublicDataUpdateRequest, RootRollupPublicInputs, makeTuple, @@ -133,8 +135,14 @@ describe('sequencer/solo_block_builder', () => { flatMap(txs, tx => tx.data.end.newNullifiers.map(x => x.toBuffer())), NULLIFIER_SUBTREE_HEIGHT, ); - for (const write of txs.flatMap(tx => tx.data.end.publicDataUpdateRequests)) { - await expectsDb.updateLeaf(MerkleTreeId.PUBLIC_DATA_TREE, write.newValue.toBuffer(), write.leafIndex.value); + for (const tx of txs) { + await expectsDb.batchInsert( + MerkleTreeId.PUBLIC_DATA_TREE, + tx.data.end.publicDataUpdateRequests.map(write => { + return new PublicDataTreeLeaf(write.leafSlot, write.newValue).toBuffer(); + }), + PUBLIC_DATA_SUBTREE_HEIGHT, + ); } }; @@ -150,7 +158,7 @@ describe('sequencer/solo_block_builder', () => { rootRollupOutput.endNullifierTreeSnapshot.root, rootRollupOutput.endContractTreeSnapshot.root, rootRollupOutput.endL1ToL2MessagesTreeSnapshot.root, - rootRollupOutput.endPublicDataTreeRoot, + rootRollupOutput.endPublicDataTreeSnapshot.root, ); await expectsDb.appendLeaves(MerkleTreeId.ARCHIVE, [blockHash.toBuffer()]); }; @@ -183,14 +191,14 @@ describe('sequencer/solo_block_builder', () => { baseRollupOutputLeft.endContractTreeSnapshot = await getTreeSnapshot(MerkleTreeId.CONTRACT_TREE); baseRollupOutputLeft.endNullifierTreeSnapshot = await getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE); baseRollupOutputLeft.endNoteHashTreeSnapshot = await getTreeSnapshot(MerkleTreeId.NOTE_HASH_TREE); - baseRollupOutputLeft.endPublicDataTreeRoot = (await getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE)).root; + baseRollupOutputLeft.endPublicDataTreeSnapshot = await getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE); // Same for the two txs on the right await updateExpectedTreesFromTxs(txsRight); baseRollupOutputRight.endContractTreeSnapshot = await getTreeSnapshot(MerkleTreeId.CONTRACT_TREE); baseRollupOutputRight.endNullifierTreeSnapshot = await getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE); baseRollupOutputRight.endNoteHashTreeSnapshot = await getTreeSnapshot(MerkleTreeId.NOTE_HASH_TREE); - baseRollupOutputRight.endPublicDataTreeRoot = (await getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE)).root; + baseRollupOutputRight.endPublicDataTreeSnapshot = await getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE); // Update l1 to l2 data tree // And update the root trees now to create proper output to the root rollup circuit @@ -198,7 +206,7 @@ describe('sequencer/solo_block_builder', () => { rootRollupOutput.endContractTreeSnapshot = await getTreeSnapshot(MerkleTreeId.CONTRACT_TREE); rootRollupOutput.endNullifierTreeSnapshot = await getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE); rootRollupOutput.endNoteHashTreeSnapshot = await getTreeSnapshot(MerkleTreeId.NOTE_HASH_TREE); - rootRollupOutput.endPublicDataTreeRoot = (await getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE)).root; + rootRollupOutput.endPublicDataTreeSnapshot = await getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE); rootRollupOutput.endL1ToL2MessagesTreeSnapshot = await getTreeSnapshot(MerkleTreeId.L1_TO_L2_MESSAGES_TREE); @@ -216,7 +224,7 @@ describe('sequencer/solo_block_builder', () => { n => new ContractData(n.contractAddress, n.portalContractAddress), ); const newPublicDataWrites = flatMap(txs, tx => - tx.data.end.publicDataUpdateRequests.map(t => new PublicDataWrite(t.leafIndex, t.newValue)), + tx.data.end.publicDataUpdateRequests.map(t => new PublicDataWrite(t.leafSlot, t.newValue)), ); const newL2ToL1Msgs = flatMap(txs, tx => tx.data.end.newL2ToL1Msgs); const newEncryptedLogs = new L2BlockL2Logs(txs.map(tx => tx.encryptedLogs || new TxL2Logs([]))); @@ -231,8 +239,8 @@ describe('sequencer/solo_block_builder', () => { endNullifierTreeSnapshot: rootRollupOutput.endNullifierTreeSnapshot, startContractTreeSnapshot: rootRollupOutput.startContractTreeSnapshot, endContractTreeSnapshot: rootRollupOutput.endContractTreeSnapshot, - startPublicDataTreeRoot: rootRollupOutput.startPublicDataTreeRoot, - endPublicDataTreeRoot: rootRollupOutput.endPublicDataTreeRoot, + startPublicDataTreeSnapshot: rootRollupOutput.startPublicDataTreeSnapshot, + endPublicDataTreeSnapshot: rootRollupOutput.endPublicDataTreeSnapshot, startL1ToL2MessagesTreeSnapshot: rootRollupOutput.startL1ToL2MessagesTreeSnapshot, endL1ToL2MessagesTreeSnapshot: rootRollupOutput.endL1ToL2MessagesTreeSnapshot, startArchiveSnapshot: rootRollupOutput.startArchiveSnapshot, @@ -363,8 +371,6 @@ describe('sequencer/solo_block_builder', () => { expect(l2Block.number).toEqual(blockNumber); }, 10_000); - // TODO(Alvaro) This test is horribly slow since it creates strictly increasing nullifiers, the worst case scenario for the simulated base rollup - // With the current implementation. it('builds a mixed L2 block', async () => { // Ensure that each transaction has unique (non-intersecting nullifier values) const txs = await Promise.all([ diff --git a/yarn-project/sequencer-client/src/block_builder/solo_block_builder.ts b/yarn-project/sequencer-client/src/block_builder/solo_block_builder.ts index 7b19203826fb..dee982a9ba0b 100644 --- a/yarn-project/sequencer-client/src/block_builder/solo_block_builder.ts +++ b/yarn-project/sequencer-client/src/block_builder/solo_block_builder.ts @@ -10,9 +10,7 @@ import { L1_TO_L2_MSG_SUBTREE_HEIGHT, L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH, MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP, - MAX_PUBLIC_DATA_READS_PER_BASE_ROLLUP, MAX_PUBLIC_DATA_READS_PER_TX, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_BASE_ROLLUP, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MembershipWitness, MergeRollupInputs, @@ -23,10 +21,14 @@ import { NULLIFIER_TREE_HEIGHT, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, NullifierLeafPreimage, + PUBLIC_DATA_SUBTREE_HEIGHT, + PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, PUBLIC_DATA_TREE_HEIGHT, PreviousKernelData, PreviousRollupData, Proof, + PublicDataTreeLeaf, + PublicDataTreeLeafPreimage, ROLLUP_VK_TREE_HEIGHT, RollupTypes, RootRollupInputs, @@ -123,7 +125,7 @@ export class SoloBlockBuilder implements BlockBuilder { endNoteHashTreeSnapshot, endNullifierTreeSnapshot, endContractTreeSnapshot, - endPublicDataTreeRoot, + endPublicDataTreeSnapshot, endL1ToL2MessagesTreeSnapshot, endArchiveSnapshot, } = circuitsOutput; @@ -136,7 +138,7 @@ export class SoloBlockBuilder implements BlockBuilder { n => new ContractData(n.contractAddress, n.portalContractAddress), ); const newPublicDataWrites = flatMap(txs, tx => - tx.data.end.publicDataUpdateRequests.map(t => new PublicDataWrite(t.leafIndex, t.newValue)), + tx.data.end.publicDataUpdateRequests.map(t => new PublicDataWrite(t.leafSlot, t.newValue)), ); const newL2ToL1Msgs = flatMap(txs, tx => tx.data.end.newL2ToL1Msgs); @@ -161,8 +163,8 @@ export class SoloBlockBuilder implements BlockBuilder { endNullifierTreeSnapshot, startContractTreeSnapshot, endContractTreeSnapshot, - startPublicDataTreeRoot: startPublicDataTreeSnapshot.root, - endPublicDataTreeRoot, + startPublicDataTreeSnapshot, + endPublicDataTreeSnapshot, startL1ToL2MessagesTreeSnapshot: startL1ToL2MessageTreeSnapshot, endL1ToL2MessagesTreeSnapshot, startArchiveSnapshot, @@ -354,7 +356,7 @@ export class SoloBlockBuilder implements BlockBuilder { this.validateTree(rollupOutput, MerkleTreeId.CONTRACT_TREE, 'ContractTree'), this.validateTree(rollupOutput, MerkleTreeId.NOTE_HASH_TREE, 'NoteHashTree'), this.validateTree(rollupOutput, MerkleTreeId.NULLIFIER_TREE, 'NullifierTree'), - this.validatePublicDataTreeRoot(rollupOutput), + this.validateTree(rollupOutput, MerkleTreeId.PUBLIC_DATA_TREE, 'PublicDataTree'), ]); } @@ -367,21 +369,6 @@ export class SoloBlockBuilder implements BlockBuilder { ]); } - /** - * Validates that the root of the public data tree matches the output of the circuit simulation. - * @param output - The output of the circuit simulation. - * Note: Public data tree is sparse, so the "next available leaf index" doesn't make sense there. - * For this reason we only validate root. - */ - protected async validatePublicDataTreeRoot(output: BaseOrMergeRollupPublicInputs | RootRollupPublicInputs) { - const localTree = await this.getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE); - const simulatedTreeRoot = output[`endPublicDataTreeRoot`]; - - if (!simulatedTreeRoot.toBuffer().equals(localTree.root.toBuffer())) { - throw new Error(`PublicData tree root mismatch (local ${localTree.root}, simulated ${simulatedTreeRoot})`); - } - } - // Helper for validating a non-roots tree against a circuit simulation output protected async validateTree( output: T, @@ -591,40 +578,87 @@ export class SoloBlockBuilder implements BlockBuilder { } protected async processPublicDataUpdateRequests(tx: ProcessedTx) { - const newPublicDataUpdateRequestsSiblingPaths: Tuple< - Tuple, - typeof MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX - > = makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, () => makeTuple(PUBLIC_DATA_TREE_HEIGHT, Fr.zero)); - for (const i in tx.data.end.publicDataUpdateRequests) { - const index = tx.data.end.publicDataUpdateRequests[i].leafIndex.value; - await this.db.updateLeaf( + const { lowLeavesWitnessData, newSubtreeSiblingPath, sortedNewLeaves, sortedNewLeavesIndexes } = + await this.db.batchInsert( MerkleTreeId.PUBLIC_DATA_TREE, - tx.data.end.publicDataUpdateRequests[i].newValue.toBuffer(), - index, - ); - const path = await this.db.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, index); - const array = path.toFieldArray(); - newPublicDataUpdateRequestsSiblingPaths[i] = makeTuple(PUBLIC_DATA_TREE_HEIGHT, j => - j < array.length ? array[j] : Fr.ZERO, + // TODO(#3675) remove oldValue from update requests + tx.data.end.publicDataUpdateRequests.map(updateRequest => { + return new PublicDataTreeLeaf(updateRequest.leafSlot, updateRequest.newValue).toBuffer(); + }), + PUBLIC_DATA_SUBTREE_HEIGHT, ); + + if (lowLeavesWitnessData === undefined) { + throw new Error(`Could not craft public data batch insertion proofs`); } - return newPublicDataUpdateRequestsSiblingPaths; + + const sortedPublicDataWrites = makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => { + return PublicDataTreeLeaf.fromBuffer(sortedNewLeaves[i]); + }); + + const sortedPublicDataWritesIndexes = makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => { + return sortedNewLeavesIndexes[i]; + }); + + const subtreeSiblingPathAsFields = newSubtreeSiblingPath.toFieldArray(); + const newPublicDataSubtreeSiblingPath = makeTuple(PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, i => { + return subtreeSiblingPathAsFields[i]; + }); + + const lowPublicDataWritesMembershipWitnesses: Tuple< + MembershipWitness, + typeof MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + > = makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => { + const witness = lowLeavesWitnessData[i]; + return MembershipWitness.fromBufferArray( + witness.index, + assertLength(witness.siblingPath.toBufferArray(), PUBLIC_DATA_TREE_HEIGHT), + ); + }); + + const lowPublicDataWritesPreimages: Tuple< + PublicDataTreeLeafPreimage, + typeof MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + > = makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => { + return lowLeavesWitnessData[i].leafPreimage as PublicDataTreeLeafPreimage; + }); + + return { + lowPublicDataWritesPreimages, + lowPublicDataWritesMembershipWitnesses, + newPublicDataSubtreeSiblingPath, + sortedPublicDataWrites, + sortedPublicDataWritesIndexes, + }; } - protected async getPublicDataReadsSiblingPaths(tx: ProcessedTx) { - const newPublicDataReadsSiblingPaths: Tuple< - Tuple, + protected async getPublicDataReadsInfo(tx: ProcessedTx) { + const newPublicDataReadsWitnesses: Tuple< + MembershipWitness, typeof MAX_PUBLIC_DATA_READS_PER_TX - > = makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, () => makeTuple(PUBLIC_DATA_TREE_HEIGHT, Fr.zero)); + > = makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, () => MembershipWitness.empty(PUBLIC_DATA_TREE_HEIGHT, 0n)); + + const newPublicDataReadsPreimages: Tuple = + makeTuple(MAX_PUBLIC_DATA_READS_PER_TX, () => PublicDataTreeLeafPreimage.empty()); for (const i in tx.data.end.publicDataReads) { - const index = tx.data.end.publicDataReads[i].leafIndex.value; - const path = await this.db.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, index); - const array = path.toFieldArray(); - newPublicDataReadsSiblingPaths[i] = makeTuple(PUBLIC_DATA_TREE_HEIGHT, j => - j < array.length ? array[j] : Fr.ZERO, + const leafSlot = tx.data.end.publicDataReads[i].leafSlot.value; + const lowLeafResult = await this.db.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot); + if (!lowLeafResult) { + throw new Error(`Public data tree should have one initial leaf`); + } + const preimage = await this.db.getLeafPreimage(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index); + const path = await this.db.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index); + newPublicDataReadsWitnesses[i] = new MembershipWitness( + PUBLIC_DATA_TREE_HEIGHT, + BigInt(lowLeafResult.index), + path.toTuple(), ); + newPublicDataReadsPreimages[i] = preimage! as PublicDataTreeLeafPreimage; } - return newPublicDataReadsSiblingPaths; + return { + newPublicDataReadsWitnesses, + newPublicDataReadsPreimages, + }; } // Builds the base rollup inputs, updating the contract, nullifier, and data trees in the process @@ -667,27 +701,14 @@ export class SoloBlockBuilder implements BlockBuilder { await this.db.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, newCommitments); - // Update the public data tree and get membership witnesses. - // All public data reads are checked against the unmodified data root when the corresponding tx started, - // so it's the unmodified tree for tx1, and the one after applying tx1 update request for tx2. - // Update requests are checked against the tree as it is iteratively updated. - // See https://github.com/AztecProtocol/aztec3-packages/issues/270#issuecomment-1522258200 - const leftPublicDataReadSiblingPaths = await this.getPublicDataReadsSiblingPaths(left); - const leftPublicDataUpdateRequestsSiblingPaths = await this.processPublicDataUpdateRequests(left); - const rightPublicDataReadSiblingPaths = await this.getPublicDataReadsSiblingPaths(right); - const rightPublicDataUpdateRequestsSiblingPaths = await this.processPublicDataUpdateRequests(right); - - const newPublicDataReadsSiblingPaths = makeTuple(MAX_PUBLIC_DATA_READS_PER_BASE_ROLLUP, i => - i < MAX_PUBLIC_DATA_READS_PER_TX - ? leftPublicDataReadSiblingPaths[i] - : rightPublicDataReadSiblingPaths[i - MAX_PUBLIC_DATA_READS_PER_TX], - ); + // The public data tree will be updated serially, first with the left TX and then with the right TX. + // The read witnesses for a given TX should be generated before the writes of the same TX are applied. + // All reads that refer to writes in the same tx are transient and can be simplified out. + const leftPublicDataReadsInfo = await this.getPublicDataReadsInfo(left); + const leftPublicDataUpdateRequestInfo = await this.processPublicDataUpdateRequests(left); - const newPublicDataUpdateRequestsSiblingPaths = makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_BASE_ROLLUP, i => - i < MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX - ? leftPublicDataUpdateRequestsSiblingPaths[i] - : rightPublicDataUpdateRequestsSiblingPaths[i - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - ); + const rightPublicDataReadsInfo = await this.getPublicDataReadsInfo(right); + const rightPublicDataUpdateRequestInfo = await this.processPublicDataUpdateRequests(right); // Update the nullifier tree, capturing the low nullifier info for each individual operation const newNullifiers = [...left.data.end.newNullifiers, ...right.data.end.newNullifiers]; @@ -719,17 +740,48 @@ export class SoloBlockBuilder implements BlockBuilder { startNullifierTreeSnapshot, startContractTreeSnapshot, startNoteHashTreeSnapshot, - startPublicDataTreeRoot: startPublicDataTreeSnapshot.root, + startPublicDataTreeSnapshot, archiveSnapshot: startArchiveSnapshot, + sortedPublicDataWrites: [ + leftPublicDataUpdateRequestInfo.sortedPublicDataWrites, + rightPublicDataUpdateRequestInfo.sortedPublicDataWrites, + ], + sortedPublicDataWritesIndexes: [ + leftPublicDataUpdateRequestInfo.sortedPublicDataWritesIndexes, + rightPublicDataUpdateRequestInfo.sortedPublicDataWritesIndexes, + ], + lowPublicDataWritesPreimages: [ + leftPublicDataUpdateRequestInfo.lowPublicDataWritesPreimages, + rightPublicDataUpdateRequestInfo.lowPublicDataWritesPreimages, + ], + lowPublicDataWritesMembershipWitnesses: [ + leftPublicDataUpdateRequestInfo.lowPublicDataWritesMembershipWitnesses, + rightPublicDataUpdateRequestInfo.lowPublicDataWritesMembershipWitnesses, + ], + publicDataWritesSubtreeSiblingPaths: [ + leftPublicDataUpdateRequestInfo.newPublicDataSubtreeSiblingPath, + rightPublicDataUpdateRequestInfo.newPublicDataSubtreeSiblingPath, + ], + sortedNewNullifiers: makeTuple(MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP, i => Fr.fromBuffer(sortedNewNullifiers[i])), sortednewNullifiersIndexes: makeTuple(MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP, i => sortednewNullifiersIndexes[i]), newCommitmentsSubtreeSiblingPath, newContractsSubtreeSiblingPath, + newNullifiersSubtreeSiblingPath: makeTuple(NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, i => i < newNullifiersSubtreeSiblingPathArray.length ? newNullifiersSubtreeSiblingPathArray[i] : Fr.ZERO, ), - newPublicDataUpdateRequestsSiblingPaths, - newPublicDataReadsSiblingPaths, + + publicDataReadsPreimages: [ + leftPublicDataReadsInfo.newPublicDataReadsPreimages, + rightPublicDataReadsInfo.newPublicDataReadsPreimages, + ], + + publicDataReadsMembershipWitnesses: [ + leftPublicDataReadsInfo.newPublicDataReadsWitnesses, + rightPublicDataReadsInfo.newPublicDataReadsWitnesses, + ], + lowNullifierLeafPreimages: makeTuple(MAX_NEW_NULLIFIERS_PER_BASE_ROLLUP, i => i < nullifierWitnessLeaves.length ? (nullifierWitnessLeaves[i].leafPreimage as NullifierLeafPreimage) diff --git a/yarn-project/sequencer-client/src/block_builder/types.ts b/yarn-project/sequencer-client/src/block_builder/types.ts index 8a645ca8c566..9f1715febb7c 100644 --- a/yarn-project/sequencer-client/src/block_builder/types.ts +++ b/yarn-project/sequencer-client/src/block_builder/types.ts @@ -3,7 +3,7 @@ import { AppendOnlyTreeSnapshot, BaseOrMergeRollupPublicInputs, RootRollupPublic /** * Type representing the names of the trees for the base rollup. */ -type BaseTreeNames = 'NoteHashTree' | 'ContractTree' | 'NullifierTree'; +type BaseTreeNames = 'NoteHashTree' | 'ContractTree' | 'NullifierTree' | 'PublicDataTree'; /** * Type representing the names of the trees. */ diff --git a/yarn-project/sequencer-client/src/sequencer/public_processor.ts b/yarn-project/sequencer-client/src/sequencer/public_processor.ts index 820da1cf0c67..333b6b83bcd8 100644 --- a/yarn-project/sequencer-client/src/sequencer/public_processor.ts +++ b/yarn-project/sequencer-client/src/sequencer/public_processor.ts @@ -217,6 +217,9 @@ export class PublicProcessor { this.patchPublicStorageActionOrdering(kernelOutput, enqueuedExecutionResult!); } + // TODO(#3675): This should be done in a public kernel circuit + this.removeRedundantPublicDataWrites(kernelOutput); + return [kernelOutput, kernelProof, newUnencryptedFunctionLogs]; } @@ -364,7 +367,7 @@ export class PublicProcessor { // Validate all items in enqueued public calls are in the kernel emitted stack const readsAreEqual = simPublicDataReads.reduce( (accum, read) => - accum && !!publicDataReads.find(item => item.leafIndex.equals(read.leafIndex) && item.value.equals(read.value)), + accum && !!publicDataReads.find(item => item.leafSlot.equals(read.leafSlot) && item.value.equals(read.value)), true, ); const updatesAreEqual = simPublicDataUpdateRequests.reduce( @@ -372,7 +375,7 @@ export class PublicProcessor { accum && !!publicDataUpdateRequests.find( item => - item.leafIndex.equals(update.leafIndex) && + item.leafSlot.equals(update.leafSlot) && item.oldValue.equals(update.oldValue) && item.newValue.equals(update.newValue), ), @@ -399,11 +402,11 @@ export class PublicProcessor { // most recently processed top/enqueued call. const numTotalReadsInKernel = arrayNonEmptyLength( publicInputs.end.publicDataReads, - f => f.leafIndex.equals(Fr.ZERO) && f.value.equals(Fr.ZERO), + f => f.leafSlot.equals(Fr.ZERO) && f.value.equals(Fr.ZERO), ); const numTotalUpdatesInKernel = arrayNonEmptyLength( publicInputs.end.publicDataUpdateRequests, - f => f.leafIndex.equals(Fr.ZERO) && f.oldValue.equals(Fr.ZERO) && f.newValue.equals(Fr.ZERO), + f => f.leafSlot.equals(Fr.ZERO) && f.oldValue.equals(Fr.ZERO) && f.newValue.equals(Fr.ZERO), ); const numReadsBeforeThisEnqueuedCall = numTotalReadsInKernel - simPublicDataReads.length; const numUpdatesBeforeThisEnqueuedCall = numTotalUpdatesInKernel - simPublicDataUpdateRequests.length; @@ -430,4 +433,22 @@ export class PublicProcessor { MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, ); } + + private removeRedundantPublicDataWrites(publicInputs: KernelCircuitPublicInputs) { + const lastWritesMap = new Map(); + for (const write of publicInputs.end.publicDataUpdateRequests) { + const key = write.leafSlot.toString(); + lastWritesMap.set(key, write); + } + + const lastWrites = publicInputs.end.publicDataUpdateRequests.filter( + write => lastWritesMap.get(write.leafSlot.toString()) === write, + ); + + publicInputs.end.publicDataUpdateRequests = padArrayEnd( + lastWrites, + PublicDataUpdateRequest.empty(), + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + ); + } } diff --git a/yarn-project/sequencer-client/src/simulator/public_executor.ts b/yarn-project/sequencer-client/src/simulator/public_executor.ts index 5999c2b03694..f8a8ce6e3823 100644 --- a/yarn-project/sequencer-client/src/simulator/public_executor.ts +++ b/yarn-project/sequencer-client/src/simulator/public_executor.ts @@ -1,6 +1,6 @@ import { CommitmentsDB, MessageLoadOracleInputs, PublicContractsDB, PublicStateDB } from '@aztec/acir-simulator'; -import { AztecAddress, EthAddress, Fr, FunctionSelector } from '@aztec/circuits.js'; -import { computePublicDataTreeIndex } from '@aztec/circuits.js/abis'; +import { AztecAddress, EthAddress, Fr, FunctionSelector, PublicDataTreeLeafPreimage } from '@aztec/circuits.js'; +import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/abis'; import { ContractDataSource, ExtendedContractData, L1ToL2MessageSource, MerkleTreeId, Tx } from '@aztec/types'; import { MerkleTreeOperations } from '@aztec/world-state'; @@ -82,17 +82,27 @@ export class WorldStatePublicDB implements PublicStateDB { * @returns The current value in the storage slot. */ public async storageRead(contract: AztecAddress, slot: Fr): Promise { - const index = computePublicDataTreeIndex(contract, slot).value; - const uncommited = this.uncommitedWriteCache.get(index); + const leafSlot = computePublicDataTreeLeafSlot(contract, slot).value; + const uncommited = this.uncommitedWriteCache.get(leafSlot); if (uncommited !== undefined) { return uncommited; } - const commited = this.commitedWriteCache.get(index); + const commited = this.commitedWriteCache.get(leafSlot); if (commited !== undefined) { return commited; } - const value = await this.db.getLeafValue(MerkleTreeId.PUBLIC_DATA_TREE, index); - return value ? Fr.fromBuffer(value) : Fr.ZERO; + + const lowLeafResult = await this.db.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot); + if (!lowLeafResult || !lowLeafResult.alreadyPresent) { + return Fr.ZERO; + } + + const preimage = (await this.db.getLeafPreimage( + MerkleTreeId.PUBLIC_DATA_TREE, + lowLeafResult.index, + )) as PublicDataTreeLeafPreimage; + + return preimage.value; } /** @@ -102,7 +112,7 @@ export class WorldStatePublicDB implements PublicStateDB { * @param newValue - The new value to store. */ public storageWrite(contract: AztecAddress, slot: Fr, newValue: Fr): Promise { - const index = computePublicDataTreeIndex(contract, slot).value; + const index = computePublicDataTreeLeafSlot(contract, slot).value; this.uncommitedWriteCache.set(index, newValue); return Promise.resolve(); } diff --git a/yarn-project/sequencer-client/src/simulator/world_state_public_db.test.ts b/yarn-project/sequencer-client/src/simulator/world_state_public_db.test.ts index 91de094d2539..8a494ba18185 100644 --- a/yarn-project/sequencer-client/src/simulator/world_state_public_db.test.ts +++ b/yarn-project/sequencer-client/src/simulator/world_state_public_db.test.ts @@ -1,5 +1,6 @@ -import { AztecAddress, Fr } from '@aztec/circuits.js'; -import { computePublicDataTreeIndex } from '@aztec/circuits.js/abis'; +import { AztecAddress, Fr, PublicDataTreeLeafPreimage } from '@aztec/circuits.js'; +import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/abis'; +import { IndexedTreeLeafPreimage } from '@aztec/foundation/trees'; import { MerkleTreeId } from '@aztec/types'; import { MerkleTreeOperations } from '@aztec/world-state'; @@ -20,22 +21,52 @@ describe('world_state_public_db', () => { addresses = Array(DB_VALUES_SIZE).fill(0).map(AztecAddress.random); slots = Array(DB_VALUES_SIZE).fill(0).map(Fr.random); dbValues = Array(DB_VALUES_SIZE).fill(0).map(Fr.random); - const publicData = new Map( - Array(DB_VALUES_SIZE) - .fill(0) - .map((_, idx: number) => { - const index = computePublicDataTreeIndex(addresses[idx], slots[idx]); - return [index.toBigInt(), dbValues[idx].toBuffer()]; - }), - ); - dbStorage = new Map>([[MerkleTreeId.PUBLIC_DATA_TREE, publicData]]); + const publicDataEntries = Array(DB_VALUES_SIZE) + .fill(0) + .map((_, idx: number) => { + const leafSlot = computePublicDataTreeLeafSlot(addresses[idx], slots[idx]); + return new PublicDataTreeLeafPreimage(leafSlot, dbValues[idx], Fr.ZERO, 0n); + }); + dbStorage = new Map>([ + [ + MerkleTreeId.PUBLIC_DATA_TREE, + new Map(publicDataEntries.map((preimage, idx) => [BigInt(idx), preimage.toBuffer()])), + ], + ]); db = mock(); - db.getLeafValue.mockImplementation((treeId: MerkleTreeId, index: bigint): Promise => { + db.getPreviousValueIndex.mockImplementation( + ( + treeId: MerkleTreeId, + leafSlot: bigint, + ): Promise< + | { + index: bigint; + alreadyPresent: boolean; + } + | undefined + > => { + const sortedByLeafSlot = publicDataEntries.slice().sort((a, b) => Number(a.getKey() - b.getKey())); + let findResult = undefined; + for (const preimage of sortedByLeafSlot) { + if (preimage.getKey() > leafSlot) { + break; + } + findResult = { + index: BigInt(publicDataEntries.indexOf(preimage)), + alreadyPresent: preimage.getKey() === leafSlot, + }; + } + + return Promise.resolve(findResult); + }, + ); + db.getLeafPreimage.mockImplementation((treeId: MerkleTreeId, index: bigint): Promise => { const tree = dbStorage.get(treeId); if (!tree) { throw new Error('Invalid Tree Id'); } - return Promise.resolve(tree.get(index)); + + return Promise.resolve(PublicDataTreeLeafPreimage.fromBuffer(tree.get(index)!)); }); }); diff --git a/yarn-project/types/src/interfaces/aztec-node.ts b/yarn-project/types/src/interfaces/aztec-node.ts index ac57cd2f3a5e..514732026ef2 100644 --- a/yarn-project/types/src/interfaces/aztec-node.ts +++ b/yarn-project/types/src/interfaces/aztec-node.ts @@ -122,9 +122,9 @@ export interface AztecNode extends StateInfoProvider { * * @param contract - Address of the contract to query. * @param slot - Slot to query. - * @returns Storage value at the given contract slot (or undefined if not found). + * @returns Storage value at the given contract slot. */ - getPublicStorageAt(contract: AztecAddress, slot: Fr): Promise; + getPublicStorageAt(contract: AztecAddress, slot: Fr): Promise; /** * Returns the current committed roots for the data trees. diff --git a/yarn-project/types/src/interfaces/index.ts b/yarn-project/types/src/interfaces/index.ts index 44ed98bbed4b..cf18112e9b44 100644 --- a/yarn-project/types/src/interfaces/index.ts +++ b/yarn-project/types/src/interfaces/index.ts @@ -7,3 +7,4 @@ export * from './node-info.js'; export * from './sync-status.js'; export * from './configs.js'; export * from './nullifier_tree.js'; +export * from './public_data_tree.js'; diff --git a/yarn-project/types/src/interfaces/public_data_tree.ts b/yarn-project/types/src/interfaces/public_data_tree.ts new file mode 100644 index 000000000000..944296fc7d14 --- /dev/null +++ b/yarn-project/types/src/interfaces/public_data_tree.ts @@ -0,0 +1,42 @@ +import { Fr, PUBLIC_DATA_TREE_HEIGHT, PublicDataTreeLeafPreimage } from '@aztec/circuits.js'; + +import { SiblingPath } from '../sibling_path.js'; + +/** + * Public data witness. + * @remarks This allows to prove either: + * - That a slot in the public data tree is empty (0 value) if it falls within the range of the leaf. + * - The current value of a slot in the public data tree if it matches exactly the slot of the leaf. + */ +export class PublicDataWitness { + constructor( + /** + * The index of the leaf in the public data tree. + */ + public readonly index: bigint, + /** + * Preimage of a low leaf. All the slots in the range of the leaf are empty, and the current value of the + * leaf slot is stored in the leaf. + */ + public readonly leafPreimage: PublicDataTreeLeafPreimage, + /** + * Sibling path to prove membership of the leaf. + */ + public readonly siblingPath: SiblingPath, + ) {} + + /** + * Returns a field array representation of a public data witness. + * @returns A field array representation of a public data witness. + */ + public toFieldArray(): Fr[] { + return [ + new Fr(this.index), + new Fr(this.leafPreimage.slot), + new Fr(this.leafPreimage.value), + new Fr(this.leafPreimage.nextIndex), + new Fr(this.leafPreimage.nextSlot), + ...this.siblingPath.toFieldArray(), + ]; + } +} diff --git a/yarn-project/types/src/interfaces/pxe.ts b/yarn-project/types/src/interfaces/pxe.ts index a78cf957bbd1..acea67622551 100644 --- a/yarn-project/types/src/interfaces/pxe.ts +++ b/yarn-project/types/src/interfaces/pxe.ts @@ -164,10 +164,10 @@ export interface PXE { * * @param contract - Address of the contract to query. * @param slot - Slot to query. - * @returns Storage value at the given contract slot (or undefined if not found). + * @returns Storage value at the given contract slot. * @throws If the contract is not deployed. */ - getPublicStorageAt(contract: AztecAddress, slot: Fr): Promise; + getPublicStorageAt(contract: AztecAddress, slot: Fr): Promise; /** * Gets notes of accounts registered in this PXE based on the provided filter. diff --git a/yarn-project/types/src/interfaces/state_info_provider.ts b/yarn-project/types/src/interfaces/state_info_provider.ts index 97616711fb9f..d643ba10b6a9 100644 --- a/yarn-project/types/src/interfaces/state_info_provider.ts +++ b/yarn-project/types/src/interfaces/state_info_provider.ts @@ -13,6 +13,7 @@ import { L2Block } from '../l2_block.js'; import { MerkleTreeId } from '../merkle_tree_id.js'; import { SiblingPath } from '../sibling_path.js'; import { NullifierMembershipWitness } from './nullifier_tree.js'; +import { PublicDataWitness } from './public_data_tree.js'; /** Helper type for a specific L2 block number or the latest block number */ type BlockNumber = number | 'latest'; @@ -132,6 +133,17 @@ export interface StateInfoProvider { nullifier: Fr, ): Promise; + /** + * Returns a public data tree witness for a given leaf slot at a given block. + * @param blockNumber - The block number at which to get the data. + * @param leafSlot - The leaf slot we try to find the witness for. + * @returns The public data witness (if found). + * @remarks The witness can be used to compute the current value of the public data tree leaf. If the low leaf preimage corresponds to an + * "in range" slot, means that the slot doesn't exist and the value is 0. If the low leaf preimage corresponds to the exact slot, the current value + * is contained in the leaf preimage. + */ + getPublicDataTreeWitness(blockNumber: BlockNumber, leafSlot: Fr): Promise; + /** * Get a block specified by its number. * @param number - The block number being requested. diff --git a/yarn-project/types/src/l2_block.ts b/yarn-project/types/src/l2_block.ts index 2918ba99bd3a..9117a01b3e44 100644 --- a/yarn-project/types/src/l2_block.ts +++ b/yarn-project/types/src/l2_block.ts @@ -78,9 +78,9 @@ export class L2Block { */ public startContractTreeSnapshot: AppendOnlyTreeSnapshot, /** - * The tree root of the public data tree at the start of the rollup. + * The tree snapshot of the public data tree at the start of the rollup. */ - public startPublicDataTreeRoot: Fr, + public startPublicDataTreeSnapshot: AppendOnlyTreeSnapshot, /** * The tree snapshot of the L2 message tree at the start of the rollup. */ @@ -102,9 +102,9 @@ export class L2Block { */ public endContractTreeSnapshot: AppendOnlyTreeSnapshot, /** - * The tree root of the public data tree at the end of the rollup. + * The tree snapshot of the public data tree at the end of the rollup. */ - public endPublicDataTreeRoot: Fr, + public endPublicDataTreeSnapshot: AppendOnlyTreeSnapshot, /** * The tree snapshot of the L2 message tree at the end of the rollup. */ @@ -214,13 +214,13 @@ export class L2Block { startNoteHashTreeSnapshot: makeAppendOnlyTreeSnapshot(0), startNullifierTreeSnapshot: makeAppendOnlyTreeSnapshot(0), startContractTreeSnapshot: makeAppendOnlyTreeSnapshot(0), - startPublicDataTreeRoot: Fr.random(), + startPublicDataTreeSnapshot: makeAppendOnlyTreeSnapshot(0), startL1ToL2MessagesTreeSnapshot: makeAppendOnlyTreeSnapshot(0), startArchiveSnapshot: makeAppendOnlyTreeSnapshot(0), endNoteHashTreeSnapshot: makeAppendOnlyTreeSnapshot(newCommitments.length), endNullifierTreeSnapshot: makeAppendOnlyTreeSnapshot(newNullifiers.length), endContractTreeSnapshot: makeAppendOnlyTreeSnapshot(newContracts.length), - endPublicDataTreeRoot: Fr.random(), + endPublicDataTreeSnapshot: makeAppendOnlyTreeSnapshot(0), endL1ToL2MessagesTreeSnapshot: makeAppendOnlyTreeSnapshot(1), endArchiveSnapshot: makeAppendOnlyTreeSnapshot(1), newCommitments, @@ -269,9 +269,9 @@ export class L2Block { */ startContractTreeSnapshot: AppendOnlyTreeSnapshot; /** - * The tree root of the public data tree at the start of the rollup. + * The tree snapshot of the public data tree at the start of the rollup. */ - startPublicDataTreeRoot: Fr; + startPublicDataTreeSnapshot: AppendOnlyTreeSnapshot; /** * The tree snapshot of the L2 message tree at the start of the rollup. */ @@ -293,9 +293,9 @@ export class L2Block { */ endContractTreeSnapshot: AppendOnlyTreeSnapshot; /** - * The tree root of the public data tree at the end of the rollup. + * The tree snapshot of the public data tree at the end of the rollup. */ - endPublicDataTreeRoot: Fr; + endPublicDataTreeSnapshot: AppendOnlyTreeSnapshot; /** * The tree snapshot of the L2 message tree at the end of the rollup. */ @@ -350,13 +350,13 @@ export class L2Block { fields.startNoteHashTreeSnapshot, fields.startNullifierTreeSnapshot, fields.startContractTreeSnapshot, - fields.startPublicDataTreeRoot, + fields.startPublicDataTreeSnapshot, fields.startL1ToL2MessagesTreeSnapshot, fields.startArchiveSnapshot, fields.endNoteHashTreeSnapshot, fields.endNullifierTreeSnapshot, fields.endContractTreeSnapshot, - fields.endPublicDataTreeRoot, + fields.endPublicDataTreeSnapshot, fields.endL1ToL2MessagesTreeSnapshot, fields.endArchiveSnapshot, fields.newCommitments, @@ -385,13 +385,13 @@ export class L2Block { this.startNoteHashTreeSnapshot, this.startNullifierTreeSnapshot, this.startContractTreeSnapshot, - this.startPublicDataTreeRoot, + this.startPublicDataTreeSnapshot, this.startL1ToL2MessagesTreeSnapshot, this.startArchiveSnapshot, this.endNoteHashTreeSnapshot, this.endNullifierTreeSnapshot, this.endContractTreeSnapshot, - this.endPublicDataTreeRoot, + this.endPublicDataTreeSnapshot, this.endL1ToL2MessagesTreeSnapshot, this.endArchiveSnapshot, this.newCommitments.length, @@ -447,13 +447,13 @@ export class L2Block { const startNoteHashTreeSnapshot = reader.readObject(AppendOnlyTreeSnapshot); const startNullifierTreeSnapshot = reader.readObject(AppendOnlyTreeSnapshot); const startContractTreeSnapshot = reader.readObject(AppendOnlyTreeSnapshot); - const startPublicDataTreeRoot = reader.readObject(Fr); + const startPublicDataTreeSnapshot = reader.readObject(AppendOnlyTreeSnapshot); const startL1ToL2MessagesTreeSnapshot = reader.readObject(AppendOnlyTreeSnapshot); const startArchiveSnapshot = reader.readObject(AppendOnlyTreeSnapshot); const endNoteHashTreeSnapshot = reader.readObject(AppendOnlyTreeSnapshot); const endNullifierTreeSnapshot = reader.readObject(AppendOnlyTreeSnapshot); const endContractTreeSnapshot = reader.readObject(AppendOnlyTreeSnapshot); - const endPublicDataTreeRoot = reader.readObject(Fr); + const endPublicDataTreeSnapshot = reader.readObject(AppendOnlyTreeSnapshot); const endL1ToL2MessagesTreeSnapshot = reader.readObject(AppendOnlyTreeSnapshot); const endArchiveSnapshot = reader.readObject(AppendOnlyTreeSnapshot); const newCommitments = reader.readVector(Fr); @@ -471,13 +471,13 @@ export class L2Block { startNoteHashTreeSnapshot, startNullifierTreeSnapshot, startContractTreeSnapshot, - startPublicDataTreeRoot, + startPublicDataTreeSnapshot, startL1ToL2MessagesTreeSnapshot: startL1ToL2MessagesTreeSnapshot, startArchiveSnapshot, endNoteHashTreeSnapshot, endNullifierTreeSnapshot, endContractTreeSnapshot, - endPublicDataTreeRoot, + endPublicDataTreeSnapshot, endL1ToL2MessagesTreeSnapshot, endArchiveSnapshot, newCommitments, @@ -587,13 +587,13 @@ export class L2Block { this.startNoteHashTreeSnapshot, this.startNullifierTreeSnapshot, this.startContractTreeSnapshot, - this.startPublicDataTreeRoot, + this.startPublicDataTreeSnapshot, this.startL1ToL2MessagesTreeSnapshot, this.startArchiveSnapshot, this.endNoteHashTreeSnapshot, this.endNullifierTreeSnapshot, this.endContractTreeSnapshot, - this.endPublicDataTreeRoot, + this.endPublicDataTreeSnapshot, this.endL1ToL2MessagesTreeSnapshot, this.endArchiveSnapshot, this.getCalldataHash(), @@ -613,7 +613,7 @@ export class L2Block { this.startNoteHashTreeSnapshot, this.startNullifierTreeSnapshot, this.startContractTreeSnapshot, - this.startPublicDataTreeRoot, + this.startPublicDataTreeSnapshot, this.startL1ToL2MessagesTreeSnapshot, this.startArchiveSnapshot, ); @@ -630,7 +630,7 @@ export class L2Block { this.endNoteHashTreeSnapshot, this.endNullifierTreeSnapshot, this.endContractTreeSnapshot, - this.endPublicDataTreeRoot, + this.endPublicDataTreeSnapshot, this.endL1ToL2MessagesTreeSnapshot, this.endArchiveSnapshot, ); @@ -841,14 +841,14 @@ export class L2Block { `startNoteHashTreeSnapshot: ${inspectTreeSnapshot(this.startNoteHashTreeSnapshot)}`, `startNullifierTreeSnapshot: ${inspectTreeSnapshot(this.startNullifierTreeSnapshot)}`, `startContractTreeSnapshot: ${inspectTreeSnapshot(this.startContractTreeSnapshot)}`, - `startPublicDataTreeRoot: ${this.startPublicDataTreeRoot.toString()}`, + `startPublicDataTreeSnapshot: ${this.startPublicDataTreeSnapshot.toString()}`, `startL1ToL2MessagesTreeSnapshot: ${inspectTreeSnapshot(this.startL1ToL2MessagesTreeSnapshot)}`, `startArchiveSnapshot: ${inspectTreeSnapshot(this.startArchiveSnapshot)}`, `endNoteHashTreeSnapshot: ${inspectTreeSnapshot(this.endNoteHashTreeSnapshot)}`, `endNullifierTreeSnapshot: ${inspectTreeSnapshot(this.endNullifierTreeSnapshot)}`, `endContractTreeSnapshot: ${inspectTreeSnapshot(this.endContractTreeSnapshot)}`, - `endPublicDataTreeRoot: ${this.endPublicDataTreeRoot.toString()}`, - `endPublicDataTreeRoot: ${this.endPublicDataTreeRoot.toString()}`, + `endPublicDataTreeSnapshot: ${this.endPublicDataTreeSnapshot.toString()}`, + `endPublicDataTreeSnapshot: ${this.endPublicDataTreeSnapshot.toString()}`, `endL1ToL2MessagesTreeSnapshot: ${inspectTreeSnapshot(this.endL1ToL2MessagesTreeSnapshot)}`, `endArchiveSnapshot: ${inspectTreeSnapshot(this.endArchiveSnapshot)}`, `newCommitments: ${inspectFrArray(this.newCommitments)}`, diff --git a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts index 5e90a48f00fb..6a5ccda9f33d 100644 --- a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts +++ b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts @@ -70,13 +70,13 @@ const getMockBlock = (blockNumber: number, newContractsCommitments?: Buffer[]) = startNoteHashTreeSnapshot: getMockTreeSnapshot(), startNullifierTreeSnapshot: getMockTreeSnapshot(), startContractTreeSnapshot: getMockTreeSnapshot(), - startPublicDataTreeRoot: Fr.random(), + startPublicDataTreeSnapshot: getMockTreeSnapshot(), startL1ToL2MessagesTreeSnapshot: getMockTreeSnapshot(), startArchiveSnapshot: getMockTreeSnapshot(), endNoteHashTreeSnapshot: getMockTreeSnapshot(), endNullifierTreeSnapshot: getMockTreeSnapshot(), endContractTreeSnapshot: getMockTreeSnapshot(), - endPublicDataTreeRoot: Fr.random(), + endPublicDataTreeSnapshot: getMockTreeSnapshot(), endL1ToL2MessagesTreeSnapshot: getMockTreeSnapshot(), endArchiveSnapshot: getMockTreeSnapshot(), newCommitments: times(MAX_NEW_COMMITMENTS_PER_TX, Fr.random), diff --git a/yarn-project/world-state/src/world-state-db/merkle_tree_db.ts b/yarn-project/world-state/src/world-state-db/merkle_tree_db.ts index 4ae2abd0bb08..cc8a8493b7c7 100644 --- a/yarn-project/world-state/src/world-state-db/merkle_tree_db.ts +++ b/yarn-project/world-state/src/world-state-db/merkle_tree_db.ts @@ -1,4 +1,8 @@ -import { MAX_NEW_NULLIFIERS_PER_TX, NullifierLeafPreimage } from '@aztec/circuits.js'; +import { + MAX_NEW_NULLIFIERS_PER_TX, + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + NullifierLeafPreimage, +} from '@aztec/circuits.js'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; import { IndexedTreeLeafPreimage } from '@aztec/foundation/trees'; @@ -8,12 +12,7 @@ import { L2Block, MerkleTreeId, SiblingPath } from '@aztec/types'; /** * Type alias for the nullifier tree ID. */ -export type IndexedTreeId = MerkleTreeId.NULLIFIER_TREE; - -/** - * Type alias for the public data tree ID. - */ -export type PublicTreeId = MerkleTreeId.PUBLIC_DATA_TREE; +export type IndexedTreeId = MerkleTreeId.NULLIFIER_TREE | MerkleTreeId.PUBLIC_DATA_TREE; /** * @@ -32,6 +31,8 @@ export type PublicTreeId = MerkleTreeId.PUBLIC_DATA_TREE; */ export const INITIAL_NULLIFIER_TREE_SIZE = 2 * MAX_NEW_NULLIFIERS_PER_TX; +export const INITIAL_PUBLIC_DATA_TREE_SIZE = 2 * MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX; + /** * Defines tree information. */ @@ -164,7 +165,7 @@ export interface MerkleTreeOperations { * @param leaf - The updated leaf value. * @param index - The index of the leaf to be updated. */ - updateLeaf(treeId: IndexedTreeId | PublicTreeId, leaf: NullifierLeafPreimage | Buffer, index: bigint): Promise; + updateLeaf(treeId: IndexedTreeId, leaf: NullifierLeafPreimage | Buffer, index: bigint): Promise; /** * Returns the index containing a leaf value. diff --git a/yarn-project/world-state/src/world-state-db/merkle_trees.ts b/yarn-project/world-state/src/world-state-db/merkle_trees.ts index 4ebcdb101c2c..2309d9de58b3 100644 --- a/yarn-project/world-state/src/world-state-db/merkle_trees.ts +++ b/yarn-project/world-state/src/world-state-db/merkle_trees.ts @@ -4,12 +4,16 @@ import { Fr, GlobalVariables, L1_TO_L2_MSG_TREE_HEIGHT, + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NOTE_HASH_TREE_HEIGHT, NULLIFIER_SUBTREE_HEIGHT, NULLIFIER_TREE_HEIGHT, NullifierLeaf, NullifierLeafPreimage, + PUBLIC_DATA_SUBTREE_HEIGHT, PUBLIC_DATA_TREE_HEIGHT, + PublicDataTreeLeaf, + PublicDataTreeLeafPreimage, } from '@aztec/circuits.js'; import { computeBlockHash, computeGlobalsHash } from '@aztec/circuits.js/abis'; import { Committable } from '@aztec/foundation/committable'; @@ -21,7 +25,6 @@ import { BatchInsertionResult, IndexedTree, Pedersen, - SparseTree, StandardIndexedTree, StandardTree, UpdateOnlyTree, @@ -37,10 +40,10 @@ import { CurrentTreeRoots, HandleL2BlockResult, INITIAL_NULLIFIER_TREE_SIZE, + INITIAL_PUBLIC_DATA_TREE_SIZE, IndexedTreeId, MerkleTreeDb, MerkleTreeOperations, - PublicTreeId, TreeInfo, } from './merkle_tree_db.js'; @@ -65,6 +68,15 @@ class NullifierTree extends StandardIndexedTree { } } +/** + * The public data tree is an indexed tree. + */ +class PublicDataTree extends StandardIndexedTree { + constructor(db: levelup.LevelUp, hasher: Hasher, name: string, depth: number, size: bigint = 0n, root?: Buffer) { + super(db, hasher, name, depth, size, PublicDataTreeLeafPreimage, PublicDataTreeLeaf, root); + } +} + /** * A convenience class for managing multiple merkle trees. */ @@ -108,12 +120,13 @@ export class MerkleTrees implements MerkleTreeDb { `${MerkleTreeId[MerkleTreeId.NOTE_HASH_TREE]}`, NOTE_HASH_TREE_HEIGHT, ); - const publicDataTree: UpdateOnlyTree = await initializeTree( - SparseTree, + const publicDataTree = await initializeTree( + PublicDataTree, this.db, hasher, `${MerkleTreeId[MerkleTreeId.PUBLIC_DATA_TREE]}`, PUBLIC_DATA_TREE_HEIGHT, + INITIAL_PUBLIC_DATA_TREE_SIZE, ); const l1Tol2MessagesTree: AppendOnlyTree = await initializeTree( StandardTree, @@ -380,7 +393,7 @@ export class MerkleTrees implements MerkleTreeDb { * @param index - The index to insert into. * @returns Empty promise. */ - public async updateLeaf(treeId: IndexedTreeId | PublicTreeId, leaf: Buffer, index: bigint): Promise { + public async updateLeaf(treeId: IndexedTreeId, leaf: Buffer, index: bigint): Promise { return await this.synchronize(() => this._updateLeaf(treeId, leaf, index)); } @@ -493,7 +506,7 @@ export class MerkleTrees implements MerkleTreeDb { return await tree.appendLeaves(leaves); } - private async _updateLeaf(treeId: IndexedTreeId | PublicTreeId, leaf: Buffer, index: bigint): Promise { + private async _updateLeaf(treeId: IndexedTreeId, leaf: Buffer, index: bigint): Promise { const tree = this.trees[treeId]; if (!('updateLeaf' in tree)) { throw new Error('Tree does not support `updateLeaf` method'); @@ -543,7 +556,7 @@ export class MerkleTrees implements MerkleTreeDb { [l2Block.endContractTreeSnapshot.root, MerkleTreeId.CONTRACT_TREE], [l2Block.endNullifierTreeSnapshot.root, MerkleTreeId.NULLIFIER_TREE], [l2Block.endNoteHashTreeSnapshot.root, MerkleTreeId.NOTE_HASH_TREE], - [l2Block.endPublicDataTreeRoot, MerkleTreeId.PUBLIC_DATA_TREE], + [l2Block.endPublicDataTreeSnapshot.root, MerkleTreeId.PUBLIC_DATA_TREE], [l2Block.endL1ToL2MessagesTreeSnapshot.root, MerkleTreeId.L1_TO_L2_MESSAGES_TREE], [l2Block.endArchiveSnapshot.root, MerkleTreeId.ARCHIVE], ] as const; @@ -577,13 +590,16 @@ export class MerkleTrees implements MerkleTreeDb { NULLIFIER_SUBTREE_HEIGHT, ); - // Sync the public data tree - for (const dataWrite of l2Block.newPublicDataWrites) { - if (dataWrite.isEmpty()) { - continue; - } - const { newValue, leafIndex } = dataWrite; - await this._updateLeaf(MerkleTreeId.PUBLIC_DATA_TREE, newValue.toBuffer(), leafIndex.value); + const publicDataTree = this.trees[MerkleTreeId.PUBLIC_DATA_TREE] as StandardIndexedTree; + + // We insert the public data tree leaves with one batch per tx to avoid updating the same key twice + for (let i = 0; i < l2Block.newPublicDataWrites.length / MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX; i++) { + await publicDataTree.batchInsert( + l2Block.newPublicDataWrites + .slice(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX * i, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX * (i + 1)) + .map(write => new PublicDataTreeLeaf(write.leafIndex, write.newValue).toBuffer()), + PUBLIC_DATA_SUBTREE_HEIGHT, + ); } // Sync and add the block to the blocks tree @@ -611,7 +627,6 @@ export class MerkleTrees implements MerkleTreeDb { this.log(`Tree ${treeName} synched with size ${info.size} root ${rootStr}`); } } - await this._snapshot(l2Block.number); return { isBlockOurs: ourBlock };