Skip to content

Commit 42d2d66

Browse files
authored
Merge pull request #75 from spacesprotocol/releases
fix: sort merged anchors by descending height, fix non-Rust client bu…
2 parents 6e58f42 + 07428a8 commit 42d2d66

8 files changed

Lines changed: 102 additions & 65 deletions

File tree

fabric/go/cmd/fabric/main.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ func main() {
5151
seeds = fabric.DefaultSeeds
5252
}
5353

54-
f := fabric.New(seeds)
54+
f := fabric.New()
55+
f.SetSeeds(seeds)
5556
if devMode {
5657
f.SetDevMode(true)
5758
}
@@ -62,14 +63,14 @@ func main() {
6263
}
6364
}
6465

65-
batch, err := f.ResolveAll(handles)
66+
zones, err := f.ResolveAll(handles)
6667
if err != nil {
6768
fmt.Fprintf(os.Stderr, "error: %s\n", err)
6869
os.Exit(1)
6970
}
7071

7172
zoneMap := make(map[string]libveritas.Zone)
72-
for _, z := range batch.Zones {
73+
for _, z := range zones {
7374
if _, exists := zoneMap[z.Handle]; !exists {
7475
zoneMap[z.Handle] = z
7576
}

fabric/go/fabric.go

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,36 @@ func (p *anchorPool) merged() (string, error) {
117117
}
118118
allEntries = append(allEntries, entries...)
119119
}
120-
data, err := json.Marshal(allEntries)
120+
121+
type entryWithHeight struct {
122+
raw json.RawMessage
123+
height uint64
124+
}
125+
parsed := make([]entryWithHeight, 0, len(allEntries))
126+
seen := make(map[uint64]struct{}, len(allEntries))
127+
for _, e := range allEntries {
128+
var meta struct {
129+
Block struct {
130+
Height uint64 `json:"height"`
131+
} `json:"block"`
132+
}
133+
if err := json.Unmarshal(e, &meta); err != nil {
134+
continue
135+
}
136+
if _, ok := seen[meta.Block.Height]; ok {
137+
continue
138+
}
139+
seen[meta.Block.Height] = struct{}{}
140+
parsed = append(parsed, entryWithHeight{raw: e, height: meta.Block.Height})
141+
}
142+
sort.Slice(parsed, func(i, j int) bool {
143+
return parsed[i].height > parsed[j].height
144+
})
145+
out := make([]json.RawMessage, len(parsed))
146+
for i, e := range parsed {
147+
out[i] = e.raw
148+
}
149+
data, err := json.Marshal(out)
121150
return string(data), err
122151
}
123152

@@ -242,7 +271,7 @@ func (f *Fabric) Badge(zone libveritas.Zone) VerificationBadge {
242271
}
243272

244273
// BadgeFor returns the verification badge given sovereignty and an anchor hash.
245-
func (f *Fabric) BadgeFor(sovereignty string, anchorHash string) VerificationBadge {
274+
func (f *Fabric) BadgeFor(sovereignty string, anchorHash []byte) VerificationBadge {
246275
f.mu.Lock()
247276
hasAny := f.trusted != nil || f.observed != nil || f.semiTrusted != nil
248277
f.mu.Unlock()
@@ -263,43 +292,31 @@ func (f *Fabric) BadgeFor(sovereignty string, anchorHash string) VerificationBad
263292
return BadgeNone
264293
}
265294

266-
func (f *Fabric) isRootTrusted(anchorHash string) bool {
295+
func (f *Fabric) isRootTrusted(anchorHash []byte) bool {
267296
f.mu.Lock()
268297
defer f.mu.Unlock()
269298
if f.trusted == nil {
270299
return false
271300
}
272-
rootBytes, err := hex.DecodeString(anchorHash)
273-
if err != nil {
274-
return false
275-
}
276-
return containsRoot(f.trusted.Roots, rootBytes)
301+
return containsRoot(f.trusted.Roots, anchorHash)
277302
}
278303

279-
func (f *Fabric) isRootObserved(anchorHash string) bool {
304+
func (f *Fabric) isRootObserved(anchorHash []byte) bool {
280305
f.mu.Lock()
281306
defer f.mu.Unlock()
282307
if f.observed == nil {
283308
return false
284309
}
285-
rootBytes, err := hex.DecodeString(anchorHash)
286-
if err != nil {
287-
return false
288-
}
289-
return containsRoot(f.observed.Roots, rootBytes)
310+
return containsRoot(f.observed.Roots, anchorHash)
290311
}
291312

292-
func (f *Fabric) isRootSemiTrusted(anchorHash string) bool {
313+
func (f *Fabric) isRootSemiTrusted(anchorHash []byte) bool {
293314
f.mu.Lock()
294315
defer f.mu.Unlock()
295316
if f.semiTrusted == nil {
296317
return false
297318
}
298-
rootBytes, err := hex.DecodeString(anchorHash)
299-
if err != nil {
300-
return false
301-
}
302-
return containsRoot(f.semiTrusted.Roots, rootBytes)
319+
return containsRoot(f.semiTrusted.Roots, anchorHash)
303320
}
304321

305322
func containsRoot(roots [][]byte, target []byte) bool {
@@ -530,8 +547,8 @@ func (f *Fabric) SearchAddr(name, addr string) ([]libveritas.Zone, error) {
530547
// Filter to zones that actually contain the matching addr record
531548
var matching []libveritas.Zone
532549
for _, z := range zones {
533-
if z.Records != nil {
534-
rs := libveritas.NewRecordSet(*z.Records)
550+
if len(z.Records) > 0 {
551+
rs := libveritas.NewRecordSet(z.Records)
535552
records, err := rs.Unpack()
536553
if err != nil {
537554
continue

fabric/js/fabric-core/src/fabric.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ export class Fabric {
144144
seen.add(h);
145145
return true;
146146
});
147+
deduped.sort((a, b) => (b.block?.height ?? b.height) - (a.block?.height ?? a.height));
147148
const anchors = this.provider.createAnchors(deduped);
148149
this.veritas = this.provider.createVeritas(anchors);
149150
}

fabric/kotlin/src/main/kotlin/org/spacesprotocol/fabric/Fabric.kt

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import kotlinx.serialization.SerialName
44
import kotlinx.serialization.Serializable
55
import kotlinx.serialization.encodeToString
66
import kotlinx.serialization.json.Json
7+
import kotlinx.serialization.json.JsonElement
8+
import kotlinx.serialization.json.jsonArray
79
import kotlinx.serialization.json.jsonObject
10+
import kotlinx.serialization.json.jsonPrimitive
811
import org.spacesprotocol.libveritas.*
912
import java.io.InputStreamReader
1013
import java.net.HttpURLConnection
@@ -60,15 +63,23 @@ private class AnchorPool {
6063
var observed: String = "" // raw entries JSON array string
6164

6265
fun merged(): String {
63-
val parts = mutableListOf<String>()
66+
val all = mutableListOf<JsonElement>()
6467
for (src in listOf(trusted, semiTrusted, observed)) {
65-
if (src.isNotEmpty()) {
66-
// Strip outer brackets and add contents
67-
val inner = src.trim().removePrefix("[").removeSuffix("]").trim()
68-
if (inner.isNotEmpty()) parts.add(inner)
69-
}
68+
if (src.isEmpty()) continue
69+
try {
70+
all.addAll(json.parseToJsonElement(src).jsonArray)
71+
} catch (_: Exception) {}
72+
}
73+
val seen = mutableSetOf<Long>()
74+
val deduped = mutableListOf<Pair<Long, JsonElement>>()
75+
for (e in all) {
76+
val h = (e as? kotlinx.serialization.json.JsonObject)
77+
?.get("block")?.jsonObject
78+
?.get("height")?.jsonPrimitive?.content?.toLongOrNull() ?: 0L
79+
if (seen.add(h)) deduped.add(h to e)
7080
}
71-
return "[${parts.joinToString(",")}]"
81+
deduped.sortByDescending { it.first }
82+
return "[${deduped.joinToString(",") { it.second.toString() }}]"
7283
}
7384
}
7485

@@ -126,7 +137,7 @@ class Fabric(
126137
fun badge(zone: Zone): VerificationBadge =
127138
badgeFor(zone.sovereignty, zone.anchorHash)
128139

129-
fun badgeFor(sovereignty: String, anchorHash: String): VerificationBadge {
140+
fun badgeFor(sovereignty: String, anchorHash: ByteArray): VerificationBadge {
130141
val hasAny = synchronized(lock) { trusted != null || observed != null || semiTrusted != null }
131142
if (!hasAny) return VerificationBadge.Unverified
132143

@@ -671,22 +682,19 @@ class Fabric(
671682

672683
// -- Private trust helpers --
673684

674-
private fun isRootTrusted(anchorHash: String): Boolean {
685+
private fun isRootTrusted(anchorHash: ByteArray): Boolean {
675686
val ts = trusted ?: return false
676-
val rootBytes = anchorHash.hexToByteArray()
677-
return ts.roots.any { it.contentEquals(rootBytes) }
687+
return ts.roots.any { it.contentEquals(anchorHash) }
678688
}
679689

680-
private fun isRootObserved(anchorHash: String): Boolean {
690+
private fun isRootObserved(anchorHash: ByteArray): Boolean {
681691
val ts = observed ?: return false
682-
val rootBytes = anchorHash.hexToByteArray()
683-
return ts.roots.any { it.contentEquals(rootBytes) }
692+
return ts.roots.any { it.contentEquals(anchorHash) }
684693
}
685694

686-
private fun isRootSemiTrusted(anchorHash: String): Boolean {
695+
private fun isRootSemiTrusted(anchorHash: ByteArray): Boolean {
687696
val ts = semiTrusted ?: return false
688-
val rootBytes = anchorHash.hexToByteArray()
689-
return ts.roots.any { it.contentEquals(rootBytes) }
697+
return ts.roots.any { it.contentEquals(anchorHash) }
690698
}
691699
}
692700

fabric/kotlin/src/main/kotlin/org/spacesprotocol/fabric/cli/Main.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,15 @@ fun main(args: Array<String>) {
5353
}
5454
}
5555

56-
val batch = try {
56+
val zones = try {
5757
fabric.resolveAll(handles)
5858
} catch (e: Exception) {
5959
System.err.println("error: $e")
6060
exitProcess(1)
6161
}
6262

6363
for (handle in handles) {
64-
val zone = batch.zones.find { it.handle == handle }
64+
val zone = zones.find { it.handle == handle }
6565
if (zone == null) {
6666
System.err.println("$handle: not found")
6767
continue

fabric/python/fabric/client.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ def merged(self) -> list:
9393
if h not in seen:
9494
seen.add(h)
9595
deduped.append(e)
96+
deduped.sort(
97+
key=lambda e: e.get("block", {}).get("height", 0) if isinstance(e, dict) else 0,
98+
reverse=True,
99+
)
96100
return deduped
97101

98102

fabric/swift/Sources/Fabric/Fabric.swift

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -84,19 +84,28 @@ private struct AnchorPool {
8484
var observed: String = "" // raw entries JSON array string
8585

8686
func merged() -> String? {
87-
var parts = [String]()
87+
var all: [Any] = []
8888
for src in [trusted, semiTrusted, observed] {
8989
if src.isEmpty { continue }
90-
let inner = src.trimmingCharacters(in: .whitespaces)
91-
.dropFirst() // remove [
92-
.dropLast() // remove ]
93-
.trimmingCharacters(in: .whitespaces)
94-
if !inner.isEmpty {
95-
parts.append(String(inner))
90+
guard let data = src.data(using: .utf8),
91+
let arr = try? JSONSerialization.jsonObject(with: data) as? [Any] else { continue }
92+
all.append(contentsOf: arr)
93+
}
94+
if all.isEmpty { return nil }
95+
var seen = Set<UInt64>()
96+
var deduped: [(UInt64, Any)] = []
97+
for e in all {
98+
let h = ((e as? [String: Any])?["block"] as? [String: Any])?["height"] as? UInt64
99+
?? UInt64(((e as? [String: Any])?["block"] as? [String: Any])?["height"] as? Int ?? 0)
100+
if seen.insert(h).inserted {
101+
deduped.append((h, e))
96102
}
97103
}
98-
if parts.isEmpty { return nil }
99-
return "[\(parts.joined(separator: ","))]"
104+
deduped.sort { $0.0 > $1.0 }
105+
let sorted = deduped.map { $0.1 }
106+
guard let data = try? JSONSerialization.data(withJSONObject: sorted),
107+
let str = String(data: data, encoding: .utf8) else { return nil }
108+
return str
100109
}
101110
}
102111

@@ -248,7 +257,7 @@ public final class Fabric: @unchecked Sendable {
248257
}
249258

250259
/// Badge given sovereignty and an anchor hash.
251-
public func badgeFor(sovereignty: String, anchorHash: String) -> VerificationBadge {
260+
public func badgeFor(sovereignty: String, anchorHash: Data) -> VerificationBadge {
252261
lock.lock()
253262
let hasAny = trusted != nil || observed != nil || semiTrusted != nil
254263
lock.unlock()
@@ -701,25 +710,22 @@ public final class Fabric: @unchecked Sendable {
701710

702711
// MARK: - Trust helpers (private)
703712

704-
private func isRootTrusted(_ anchorHash: String) -> Bool {
713+
private func isRootTrusted(_ anchorHash: Data) -> Bool {
705714
lock.lock(); defer { lock.unlock() }
706715
guard let ts = trusted else { return false }
707-
guard let rootBytes = Data(hexString: anchorHash) else { return false }
708-
return ts.roots.contains { Data($0) == rootBytes }
716+
return ts.roots.contains { Data($0) == anchorHash }
709717
}
710718

711-
private func isRootObserved(_ anchorHash: String) -> Bool {
719+
private func isRootObserved(_ anchorHash: Data) -> Bool {
712720
lock.lock(); defer { lock.unlock() }
713721
guard let ts = observed else { return false }
714-
guard let rootBytes = Data(hexString: anchorHash) else { return false }
715-
return ts.roots.contains { Data($0) == rootBytes }
722+
return ts.roots.contains { Data($0) == anchorHash }
716723
}
717724

718-
private func isRootSemiTrusted(_ anchorHash: String) -> Bool {
725+
private func isRootSemiTrusted(_ anchorHash: Data) -> Bool {
719726
lock.lock(); defer { lock.unlock() }
720727
guard let ts = semiTrusted else { return false }
721-
guard let rootBytes = Data(hexString: anchorHash) else { return false }
722-
return ts.roots.contains { Data($0) == rootBytes }
728+
return ts.roots.contains { Data($0) == anchorHash }
723729
}
724730

725731
// MARK: - Internal fetch helpers

fabric/swift/Sources/FabricCLI/FabricCLI.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ struct FabricCLI {
4545
try await fabric.trust(trustId)
4646
}
4747

48-
let batch = try await fabric.resolveAll(handles)
48+
let zones = try await fabric.resolveAll(handles)
4949

5050
for handle in handles {
51-
guard let zone = batch.zones.first(where: { $0.handle == handle }) else {
51+
guard let zone = zones.first(where: { $0.handle == handle }) else {
5252
fputs("\(handle): not found\n", stderr)
5353
continue
5454
}

0 commit comments

Comments
 (0)