diff --git a/rhp/v2/merkle.go b/rhp/v2/merkle.go index ba026283..a676295d 100644 --- a/rhp/v2/merkle.go +++ b/rhp/v2/merkle.go @@ -435,6 +435,29 @@ func VerifyAppendProof(numLeaves uint64, treeHashes []types.Hash256, sectorRoot, return acc.root() == newRoot } +// DiffProofSize returns the size of a Merkle diff proof for the specified +// actions within a tree containing numLeaves leaves. +func DiffProofSize(actions []RPCWriteAction, numLeaves uint64) (numHashes int) { + indices := sectorsChanged(actions, numLeaves) + numHashes += len(indices) + + buildRange := func(i, j uint64) { + for i < j { + subtreeSize := nextSubtreeSize(i, j) + numHashes++ + i += subtreeSize + } + } + + var start uint64 + for _, end := range indices { + buildRange(start, end) + start = end + 1 + } + buildRange(start, numLeaves) + return +} + // BuildDiffProof constructs a diff proof for the specified actions. // ActionUpdate is not supported. func BuildDiffProof(actions []RPCWriteAction, sectorRoots []types.Hash256) (treeHashes, leafHashes []types.Hash256) { diff --git a/v2/net/rhp/merkle.go b/v2/net/rhp/merkle.go index 3547acb7..5067ac5e 100644 --- a/v2/net/rhp/merkle.go +++ b/v2/net/rhp/merkle.go @@ -6,6 +6,7 @@ import ( "io" "math" "math/bits" + "sort" "unsafe" "go.sia.tech/core/v2/internal/blake2b" @@ -244,8 +245,61 @@ func RangeProofSize(n, start, end uint64) uint64 { // DiffProofSize returns the size of a Merkle diff proof for the specified // actions within a tree containing n leaves. -func DiffProofSize(n int, actions []RPCWriteAction) int { - return 128 // TODO +func DiffProofSize(n uint64, actions []RPCWriteAction) (numHashes int) { + indices := sectorsChanged(actions, uint64(n)) + numHashes += len(indices) + + buildRange := func(i, j uint64) { + for i < j { + subtreeSize := nextSubtreeSize(i, j) + numHashes++ + i += subtreeSize + } + } + + var start uint64 + for _, end := range indices { + buildRange(start, end) + start = end + 1 + } + buildRange(start, n) + return +} + +func sectorsChanged(actions []RPCWriteAction, numSectors uint64) []uint64 { + newNumSectors := numSectors + sectorsChanged := make(map[uint64]struct{}) + for _, action := range actions { + switch action.Type { + case RPCWriteActionAppend: + sectorsChanged[newNumSectors] = struct{}{} + newNumSectors++ + + case RPCWriteActionTrim: + for i := uint64(0); i < action.A; i++ { + newNumSectors-- + sectorsChanged[newNumSectors] = struct{}{} + } + + case RPCWriteActionSwap: + sectorsChanged[action.A] = struct{}{} + sectorsChanged[action.B] = struct{}{} + + default: + panic("unknown or unsupported action type: " + action.Type.String()) + } + } + + var sectorIndices []uint64 + for index := range sectorsChanged { + if index < numSectors { + sectorIndices = append(sectorIndices, index) + } + } + sort.Slice(sectorIndices, func(i, j int) bool { + return sectorIndices[i] < sectorIndices[j] + }) + return sectorIndices } // nextSubtreeSize returns the size of the subtree adjacent to start that does diff --git a/v2/net/rhp/rpc.go b/v2/net/rhp/rpc.go index 38fe376c..c8e7745a 100644 --- a/v2/net/rhp/rpc.go +++ b/v2/net/rhp/rpc.go @@ -231,7 +231,7 @@ func RPCWriteRenterCost(settings HostSettings, fc types.FileContract, actions [] sectorStoragePrice := settings.StoragePrice.Mul64(SectorSize).Mul64(storageDuration) storageCost = sectorStoragePrice.Mul64(sectorsAdded - sectorsRemoved) } - proofSize := DiffProofSize(int(fc.Filesize/SectorSize), actions) + proofSize := DiffProofSize(fc.Filesize/SectorSize, actions) downloadBandwidth := uint64(proofSize) * 32 return settings.InstrWriteBaseCost. Add(settings.UploadBandwidthPrice.Mul64(sectorsAdded * SectorSize)).