Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: increase tree leaf log2 size to 5 #68

Merged
merged 6 commits into from
Aug 11, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly-008922d5165c764859bc540d7298045eebf5bc60
version: nightly-fe2acca4e379793539db80e032d76ffe0110298b

- run: forge build
- run: forge fmt --check src test
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly-008922d5165c764859bc540d7298045eebf5bc60
version: nightly-fe2acca4e379793539db80e032d76ffe0110298b

- name: Run all tests
run: make test-all
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ test/uarch-log
*.log
test/UArchReplay_*.t.sol
.DS_Store
tests/*
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ TEST_DIR := test
DOWNLOADDIR := downloads
SRC_DIR := src

EMULATOR_VERSION ?= v0.17.0
EMULATOR_VERSION ?= v0.18.0-test5

TESTS_DATA_FILE ?= cartesi-machine-tests-data-$(EMULATOR_VERSION).deb
TESTS_DATA_DOWNLOAD_URL := https://github.com/cartesi/machine-emulator/releases/download/$(EMULATOR_VERSION)/$(TESTS_DATA_FILE)
Expand Down
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ libs = ["node_modules", "lib"]
gas_reports = ["*"]
memory_limit = 11149934592
fs_permissions = [{access = "read", path = "./test/uarch-log/"}, {access = "read", path = "./test/uarch-bin/"}]
gas_limit = "18446744073709551615"

[fmt]
line_length = 80
Expand Down
4 changes: 2 additions & 2 deletions shasum-download
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
d3b254f274fd6c32e6688365886fbe6b86f91aab733a83cdb73e2461ed7a0d96 downloads/cartesi-machine-tests-data-v0.17.0.deb
8f0cd797b78466b62184257b3276d6c029c5ea5a53672036ad507cbf417d0211 downloads/uarch-riscv-tests-json-logs-v0.17.0.tar.gz
e2dcd5598a67844d6a1199189089f2cc9ca306fcb80c45a634376861c89e61de downloads/cartesi-machine-tests-data-v0.18.0-test5.deb
d167c42c61a1753190b28461fbe018d4620a2379afc982ed468e3422dd27c8ba downloads/uarch-riscv-tests-json-logs-v0.18.0-test5.tar.gz
71 changes: 58 additions & 13 deletions src/AccessLogs.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import "./UArchConstants.sol";
library AccessLogs {
using Buffer for Buffer.Context;
using Memory for Memory.AlignedSize;
using Memory for Memory.PhysicalAddress;

struct Context {
bytes32 currentRootHash;
Expand Down Expand Up @@ -61,9 +62,9 @@ library AccessLogs {
return end2;
}

/// @dev bytes buffer layouts differently for `readWord` and `writeWord`,
///`readWord` [8 bytes as uint64 value, 32 bytes as drive hash, 61 * 32 bytes as sibling proofs]
///`writeWord` [32 bytes as old drive hash, 32 * 61 bytes as sibling proofs]
/// @dev bytes buffer layout is the different for `readWord` and `writeWord`,
/// readWord: [32 bytes as read data], [59 * 32 bytes as sibling hashes]
/// writeWord [32 bytes as written hash] [32 bytes as read data], [59 * 32 bytes as sibling hashes]

//
// Read methods
Expand Down Expand Up @@ -94,13 +95,19 @@ library AccessLogs {
AccessLogs.Context memory a,
Memory.PhysicalAddress readAddress
) internal pure returns (uint64) {
bytes8 b8 = a.buffer.consumeBytes8();
bytes32 valHash = keccak256(abi.encodePacked(b8));
bytes32 expectedValHash =
readLeaf(a, Memory.strideFromWordAddress(readAddress));
bytes32 readData = a.buffer.consumeBytes32();
bytes32 computedReadHash = keccak256(abi.encodePacked(readData));
(Memory.PhysicalAddress leafAddress, uint64 wordOffset) =
readAddress.truncateToLeaf();
bytes8 word = getBytes8FromBytes32AtOffset(readData, wordOffset);

require(valHash == expectedValHash, "Read value doesn't match");
return machineWordToSolidityUint64(b8);
bytes32 expectedReadHash =
readLeaf(a, Memory.strideFromLeafAddress(leafAddress));

require(
computedReadHash == expectedReadHash, "Read value doesn't match"
);
return machineWordToSolidityUint64(word);
}

//
Expand Down Expand Up @@ -138,10 +145,48 @@ library AccessLogs {
Memory.PhysicalAddress writeAddress,
uint64 newValue
) internal pure {
writeLeaf(
a,
Memory.strideFromWordAddress(writeAddress),
keccak256(abi.encodePacked(solidityUint64ToMachineWord(newValue)))
(Memory.PhysicalAddress leafAddress, uint64 wordOffset) =
mpernambuco marked this conversation as resolved.
Show resolved Hide resolved
writeAddress.truncateToLeaf();
bytes32 writtenHash = a.buffer.consumeBytes32();
bytes32 readData = a.buffer.consumeBytes32();

// check if read data hashes to the same read hash that is next in the buffer
bytes32 computedReadHash = keccak256(abi.encodePacked(readData));
bytes32 loggedReadHash = a.buffer.peekBytes32();
require(
computedReadHash == loggedReadHash,
"logged and computed read hashes mismatch"
);

// construct the written data by patching the verified read data
bytes32 computedWrittenData = setBytes8InBytes32AtOffset(
readData, wordOffset, solidityUint64ToMachineWord(newValue)
);
bytes32 computedWrittenHash =
keccak256(abi.encodePacked(computedWrittenData));
require(computedWrittenHash == writtenHash, "Written hash mismatch");

writeLeaf(a, Memory.strideFromLeafAddress(leafAddress), writtenHash);
}

function getBytes8FromBytes32AtOffset(bytes32 source, uint64 offset)
internal
pure
returns (bytes8)
{
return bytes8(source << (offset << Memory.LOG2_WORD));
}

function setBytes8InBytes32AtOffset(
bytes32 target,
uint64 offset,
bytes8 source
) internal pure returns (bytes32) {
bytes32 erase_mask = bytes32(bytes8(type(uint64).max));
erase_mask = erase_mask >> (offset << Memory.LOG2_WORD);
erase_mask = ~erase_mask;
bytes32 result = target & erase_mask;
result = result | (bytes32(source) >> (offset << Memory.LOG2_WORD));
return result;
}
}
9 changes: 5 additions & 4 deletions src/Buffer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,13 @@ library Buffer {
) internal pure returns (bytes32, uint8) {
// require that multiplier makes sense!
uint8 logOfSize = region.alignedSize.log2();
require(logOfSize <= LOG2RANGE, "Cannot be bigger than the tree itself");
require(
logOfSize <= Memory.LOG2_MAX_SIZE,
"Cannot be bigger than the tree itself"
);

uint64 stride = Memory.Stride.unwrap(region.stride);
uint8 nodesCount = LOG2RANGE - logOfSize;
uint8 nodesCount = Memory.LOG2_MAX_SIZE - logOfSize;

for (uint64 i = 0; i < nodesCount; i++) {
Buffer.Context memory siblings =
Expand Down Expand Up @@ -113,8 +116,6 @@ library Buffer {
return root;
}

uint8 constant LOG2RANGE = 61;

function isEven(uint64 x) private pure returns (bool) {
return x % 2 == 0;
}
Expand Down
45 changes: 35 additions & 10 deletions src/Memory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ pragma solidity ^0.8.0;
library Memory {
// Specifies a memory region and it's merkle hash.
// The size is given in the number of leaves in the tree,
// and therefore are word-sized.
// This means a `alignedSize` specifies a region the size of a word.
// and therefore are leaf-sized.
// This means a `alignedSize` specifies a region the size of a leaf.
// The address has to be aligned to a power-of-two.
// By using an stride, we can guarantee that the address is aligned.
// The address is given by `stride * (1 << log2s)`.
Expand All @@ -45,15 +45,14 @@ library Memory {
return regionFromStride(stride, alignedSize);
}

function regionFromWordAddress(PhysicalAddress startAddress)
function regionFromLeafAddress(PhysicalAddress startAddress)
internal
pure
returns (Region memory)
{
return regionFromPhysicalAddress(startAddress, alignedSizeFromLog2(0));
}

//
// Stride and PhysicalAddress
//
// When using memory address and a size in merkle trees to refer to a memory region,
Expand All @@ -74,18 +73,21 @@ library Memory {
) internal pure returns (Stride) {
uint64 s = alignedSize.size();
uint64 addr = PhysicalAddress.unwrap(startAddress);
// assert memory address is word-aligned (8-byte long)
assert(addr & 7 == 0);
uint64 position = PhysicalAddress.unwrap(startAddress) >> 3;

// assert memory address is leaf-aligned (32-byte long)
assert(addr & LEAF_MASK == 0);
uint64 position = PhysicalAddress.unwrap(startAddress) >> LOG2_LEAF;

// assert position and size are aligned
// position has to be a multiple of size
// equivalent to: size = 2^a, position = 2^b, position = size * 2^c, where c >= 0
assert(((s - 1) & position) == 0);
uint64 stride = position / s;

return Stride.wrap(stride);
}

function strideFromWordAddress(PhysicalAddress startAddress)
function strideFromLeafAddress(PhysicalAddress startAddress)
internal
pure
returns (Stride)
Expand All @@ -101,15 +103,18 @@ library Memory {
assert(Stride.unwrap(stride) * s < MAX_STRIDE);
}

//
// AlignedSize
//
// The size is given in the number of leaves in the tree,
// and therefore are word-sized; a size of one (or log2size zero) means one word long.

type AlignedSize is uint8;

uint64 constant MAX_SIZE = (1 << 61);
uint8 constant LOG2_WORD = 3;
uint8 constant LOG2_LEAF = 5;
uint8 constant LOG2_MAX_SIZE = 64 - LOG2_LEAF;
uint64 constant LEAF_MASK = uint64(1 << LOG2_LEAF) - 1;
uint64 constant MAX_SIZE = uint64(1 << LOG2_MAX_SIZE);

using Memory for AlignedSize;

Expand All @@ -134,4 +139,24 @@ library Memory {
{
return PhysicalAddress.wrap(uint64Address);
}

function truncateToLeaf(PhysicalAddress addr)
internal
pure
returns (PhysicalAddress, uint64)
{
uint64 r = Memory.PhysicalAddress.unwrap(addr) & ~LEAF_MASK;
PhysicalAddress truncated = Memory.PhysicalAddress.wrap(r);
uint64 offset = minus(addr, truncated);
return (truncated, offset);
}

function minus(PhysicalAddress lhs, PhysicalAddress rhs)
internal
pure
returns (uint64)
{
return Memory.PhysicalAddress.unwrap(lhs)
- Memory.PhysicalAddress.unwrap(rhs);
}
}
2 changes: 1 addition & 1 deletion src/UArchCompat.sol
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ library UArchCompat {
Memory.regionFromPhysicalAddress(
UArchConstants.RESET_POSITION.toPhysicalAddress(),
Memory.alignedSizeFromLog2(
UArchConstants.RESET_ALIGNED_SIZE - 3
UArchConstants.RESET_ALIGNED_SIZE - Memory.LOG2_LEAF
)
),
UArchConstants.PRESTINE_STATE
Expand Down
2 changes: 1 addition & 1 deletion src/UArchConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ library UArchConstants {
uint64 constant RESET_POSITION = 0x400000;
uint8 constant RESET_ALIGNED_SIZE = 22;
bytes32 constant PRESTINE_STATE =
0xc2b9cf2658f233f44c403aba6e9f9aa6709e00617b349c0190113517f03d13de;
0x4de6115bdadc23724cf20c5580d718525ce81b294c8c149d3658020c380df109;
uint64 constant UARCH_ECALL_FN_HALT = 1;
uint64 constant UARCH_ECALL_FN_PUTCHAR = 2;
// END OF AUTO-GENERATED CODE
Expand Down
63 changes: 49 additions & 14 deletions templates/AccessLogs.sol.template
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import "./UArchConstants.sol";
library AccessLogs {
using Buffer for Buffer.Context;
using Memory for Memory.AlignedSize;
using Memory for Memory.PhysicalAddress;

struct Context {
bytes32 currentRootHash;
Expand Down Expand Up @@ -74,9 +75,9 @@ library AccessLogs {

//:#ifndef test

/// @dev bytes buffer layouts differently for `readWord` and `writeWord`,
///`readWord` [8 bytes as uint64 value, 32 bytes as drive hash, 61 * 32 bytes as sibling proofs]
///`writeWord` [32 bytes as old drive hash, 32 * 61 bytes as sibling proofs]
/// @dev bytes buffer layout is the different for `readWord` and `writeWord`,
/// readWord: [32 bytes as read data], [59 * 32 bytes as sibling hashes]
/// writeWord [32 bytes as written hash] [32 bytes as read data], [59 * 32 bytes as sibling hashes]

//
// Read methods
Expand Down Expand Up @@ -107,13 +108,16 @@ library AccessLogs {
AccessLogs.Context memory a,
Memory.PhysicalAddress readAddress
) internal pure returns (uint64) {
bytes8 b8 = a.buffer.consumeBytes8();
bytes32 valHash = keccak256(abi.encodePacked(b8));
bytes32 expectedValHash =
readLeaf(a, Memory.strideFromWordAddress(readAddress));
bytes32 readData = a.buffer.consumeBytes32();
bytes32 computedReadHash = keccak256(abi.encodePacked(readData));
(Memory.PhysicalAddress leafAddress, uint64 wordOffset) = readAddress.truncateToLeaf();
bytes8 word = getBytes8FromBytes32AtOffset(readData, wordOffset);

require(valHash == expectedValHash, "Read value doesn't match");
return machineWordToSolidityUint64(b8);
bytes32 expectedReadHash =
readLeaf(a, Memory.strideFromLeafAddress(leafAddress));

require(computedReadHash == expectedReadHash, "Read value doesn't match");
return machineWordToSolidityUint64(word);
}

//
Expand Down Expand Up @@ -151,11 +155,42 @@ library AccessLogs {
Memory.PhysicalAddress writeAddress,
uint64 newValue
) internal pure {
writeLeaf(
a,
Memory.strideFromWordAddress(writeAddress),
keccak256(abi.encodePacked(solidityUint64ToMachineWord(newValue)))
);
(Memory.PhysicalAddress leafAddress, uint64 wordOffset) = writeAddress.truncateToLeaf();
bytes32 writtenHash = a.buffer.consumeBytes32();
bytes32 readData = a.buffer.consumeBytes32();

// check if read data hashes to the same read hash that is next in the buffer
bytes32 computedReadHash = keccak256(abi.encodePacked(readData));
bytes32 loggedReadHash = a.buffer.peekBytes32();
require(computedReadHash ==loggedReadHash, "logged and computed read hashes mismatch");

// construct the written data by patching the verified read data
bytes32 computedWrittenData = setBytes8InBytes32AtOffset(readData, wordOffset, solidityUint64ToMachineWord(newValue));
bytes32 computedWrittenHash = keccak256(abi.encodePacked(computedWrittenData));
require(computedWrittenHash == writtenHash, "Written hash mismatch");

writeLeaf(a, Memory.strideFromLeafAddress(leafAddress), writtenHash);
}

function getBytes8FromBytes32AtOffset(bytes32 source, uint64 offset)
internal
pure
returns (bytes8)
{
return bytes8(source << (offset << Memory.LOG2_WORD));
}

function setBytes8InBytes32AtOffset(bytes32 target, uint64 offset, bytes8 source)
internal
pure
returns (bytes32)
{
bytes32 erase_mask = bytes32(bytes8(type(uint64).max));
erase_mask = erase_mask >> (offset << Memory.LOG2_WORD);
erase_mask = ~erase_mask;
bytes32 result = target & erase_mask;
result = result | (bytes32(source) >> (offset << Memory.LOG2_WORD));
return result;
}

//:#else
Expand Down
Loading
Loading