Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
17 changes: 17 additions & 0 deletions pkg/accumulate/dn_search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@ func TestADNBlockIsSearchedThroughEveryAnchoredBlock(t *testing.T) {
if len(txs) != 1 || !strings.EqualFold(txs[0].Hash, intentTx) || txs[0].BlockHeight != dnH || txs[0].AccountURL != "acc://user.acme/data" {
t.Fatalf("found %+v; want the one intent, at DN height %d", txs, dnH)
}
// RB4-F46: the BVN the transaction was written on, and its block there - the L1-L3 proof is built on it. Both
// were dropped, so every intent was proved on "acc://dn.acme" and no proof could be built.
if txs[0].ProofPartition != "bvn1" || txs[0].ProofBlockIndex != bvn1Block {
t.Fatalf("the intent was written on bvn1 block %d; it carries proof partition %q block %d", bvn1Block, txs[0].ProofPartition, txs[0].ProofBlockIndex)
}
// The BVNs were read at their own block, never at the DN's height.
for _, q := range f.blockQueries {
if strings.HasPrefix(q, "acc://bvn") && strings.Contains(q, fmt.Sprintf("|%d|", dnH)) {
Expand Down Expand Up @@ -270,3 +275,15 @@ func TestAnAnchorEntryThatIsNotAnAnchorIsAnError(t *testing.T) {
t.Fatal("an anchor-pool entry that is not an anchor was skipped")
}
}

// RB4-F46: a BVN partition name is read from its partition URL exactly; anything else is no BVN, never a default.
func TestTheBVNNameIsReadFromItsPartitionURL(t *testing.T) {
for in, want := range map[string]string{
"acc://bvn-BVN1.acme": "bvn1", "acc://bvn-bvn0.acme": "bvn0", "acc://BVN-Apollo.acme": "apollo",
"acc://dn.acme": "", "acc://bvn-.acme": "", "acc://bvn-BVN1.acme/ledger": "", "": "", "bvn1": "",
} {
if got := BVNNameOf(in); got != want {
t.Errorf("BVNNameOf(%q) = %q, want %q", in, got, want)
}
}
}
35 changes: 32 additions & 3 deletions pkg/accumulate/liteclient_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,9 +352,28 @@ type CertenTransaction struct {
Timestamp time.Time `json:"timestamp"`
IntentData map[string]interface{} `json:"intent_data"`
TransactionType string `json:"transaction_type"`
// Legacy fields for backward compatibility
// Partition is the partition whose block BlockHeight counts: the Directory Network block the intent was
// discovered in (it anchored the BVN block that carried it). Batch settlement keeps the two as one pair.
Partition string `json:"partition,omitempty"`
RawTx map[string]interface{} `json:"raw_tx,omitempty"`
// ProofPartition is the BVN the transaction was written on ("bvn1") and ProofBlockIndex its block there: an
// L1-L3 proof is built on that BVN (RB4-F46). Empty when the entry was not read from a BVN.
ProofPartition string `json:"proof_partition,omitempty"`
ProofBlockIndex int64 `json:"proof_block_index,omitempty"`
}

// BVNNameOf reads a BVN's partition name from its partition URL, acc://bvn-<ID>.acme -> lower(<ID>), exactly.
// Anything else - the Directory Network, a ledger URL, a malformed name - is no BVN: "".
func BVNNameOf(partitionURL string) string {
u := strings.ToLower(strings.TrimSpace(partitionURL))
if !strings.HasPrefix(u, "acc://bvn-") || !strings.HasSuffix(u, ".acme") {
return ""
}
id := strings.TrimSuffix(strings.TrimPrefix(u, "acc://bvn-"), ".acme")
if id == "" || strings.ContainsAny(id, "/.@") {
return ""
}
return id
}

// isCertenTransaction checks if a block entry is a CERTEN intent transaction
Expand Down Expand Up @@ -635,6 +654,10 @@ func (l *LiteClientAdapter) parseCertenTransaction(entry BlockEntry, block *Mino
RawTx: entry.Data,
IntentData: make(map[string]interface{}),
}
if bvn := BVNNameOf(entry.Partition); bvn != "" {
certenTx.ProofPartition = bvn
certenTx.ProofBlockIndex = entry.PartitionBlock
}

// Try to extract intent data from the correct transaction structure
intentData := l.extractIntentDataFromEntry(entry)
Expand Down Expand Up @@ -1232,6 +1255,10 @@ type BlockEntry struct {
Index int `json:"index"`
Type string `json:"type"`
Data map[string]interface{} `json:"data"`
// Partition and PartitionBlock are the partition URL the entry was read from and its block there
// (RB4-F46: a DN block's transactions are read from the BVN blocks it anchored, and which BVN was lost).
Partition string `json:"partition,omitempty"`
PartitionBlock int64 `json:"partition_block,omitempty"`
}

// parseMinorBlockRecord parses a single MinorBlockRecord from the v3 API response
Expand Down Expand Up @@ -1308,8 +1335,10 @@ func (l *LiteClientAdapter) getBlockEntries(blockData map[string]interface{}, bl
for entryIdx, entry := range allEntries {
if entryMap, ok := entry.(map[string]interface{}); ok {
blockEntry := BlockEntry{
Index: entryIdx,
Data: entryMap,
Index: entryIdx,
Data: entryMap,
Partition: partition,
PartitionBlock: blockHeight,
}

// Extract entry type if available
Expand Down
14 changes: 8 additions & 6 deletions pkg/consensus/async_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -454,9 +454,10 @@ func (bv *BFTValidator) RunProofCycle(
commitment,
att.CertenIntent.AccountURL,
att.CertenIntent.TransactionHash,
// The partition the transaction was discovered on - the chain's answer, and the one consensus
// built its proof on. It used to be "", leaving the cycle to recompute it (RB3-F89).
att.CertenIntent.Partition,
// The BVN the transaction was written on - the chain's answer, and the one consensus built its
// proof on (RB4-F46: this was the Directory Network partition discovery found it through). It used
// to be "", leaving the cycle to recompute it (RB3-F89).
att.CertenIntent.ProofPartition,
); err != nil {
// The orchestrator records the refusal as the member's outcome where the member can be placed
// (RB3-F103); this line is the validator's own record of it.
Expand Down Expand Up @@ -636,9 +637,10 @@ func (bv *BFTValidator) recordFailedProofCycle(
commitment,
att.CertenIntent.AccountURL,
att.CertenIntent.TransactionHash,
// The partition the transaction was discovered on - the chain's answer, and the one consensus
// built its proof on. It used to be "", leaving the cycle to recompute it (RB3-F89).
att.CertenIntent.Partition,
// The BVN the transaction was written on - the chain's answer, and the one consensus built its
// proof on (RB4-F46: this was the Directory Network partition discovery found it through). It used
// to be "", leaving the cycle to recompute it (RB3-F89).
att.CertenIntent.ProofPartition,
); err != nil {
bv.logger.Printf("⚠️ [PROOF-CYCLE] could not record the failure of intent %s: %v — the "+
"intent is settled nowhere AND recorded nowhere, which needs operator attention",
Expand Down
14 changes: 9 additions & 5 deletions pkg/consensus/intent.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,15 @@ type CertenIntent struct {
TransactionHash string `json:"transactionHash"`
AccountURL string `json:"accountUrl"` // Principal account URL (where TX lives): .../data
OrganizationADI string `json:"organizationAdi"` // Organization ADI (for policy/routing): org ADI only
Partition string `json:"partition"` // BVN partition name (e.g., "bvn1") for L1-L3 proof generation
IntentData []byte `json:"intentData"` // Raw JSON blob - canonicalized later in commitment pipeline
CrossChainData []byte `json:"crossChainData"` // Raw JSON blob - canonicalized later in commitment pipeline
GovernanceData []byte `json:"governanceData"` // Raw JSON blob - canonicalized later in commitment pipeline
ReplayData []byte `json:"replayData"` // Raw JSON blob - canonicalized later in commitment pipeline
// Partition is the partition whose block the intent was discovered in (the Directory Network block that
// anchored it), paired with the discovery height for batch settlement's commit block.
Partition string `json:"partition"`
// ProofPartition is the BVN the intent was written on ("bvn1"): the L1-L3 proof is built on it (RB4-F46).
ProofPartition string `json:"proof_partition,omitempty"`
IntentData []byte `json:"intentData"` // Raw JSON blob - canonicalized later in commitment pipeline
CrossChainData []byte `json:"crossChainData"` // Raw JSON blob - canonicalized later in commitment pipeline
GovernanceData []byte `json:"governanceData"` // Raw JSON blob - canonicalized later in commitment pipeline
ReplayData []byte `json:"replayData"` // Raw JSON blob - canonicalized later in commitment pipeline

// CRITICAL: Proof class determines execution routing per FIRST_PRINCIPLES 2.5
// On-demand vs on-cadence proofs are NEVER interchangeable
Expand Down
5 changes: 3 additions & 2 deletions pkg/execution/proof_partition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@ func TestAProofIsNeverBuiltOnAGuessedPartition(t *testing.T) {
}
}

// The proof cycle is started with the partition consensus used, not "".
// The proof cycle is started with the partition consensus used, not "": the BVN the transaction was written on
// (RB4-F46 - the intent Partition holds the Directory Network block discovery found it through).
func TestTheProofCycleIsGivenTheDiscoveredPartition(t *testing.T) {
raw, err := os.ReadFile("../consensus/async_attestation.go")
if err != nil {
t.Fatal(err)
}
src := string(raw)
if n := strings.Count(src, "att.CertenIntent.Partition,\n\t); err != nil {"); n != 2 {
if n := strings.Count(src, "att.CertenIntent.ProofPartition,\n\t); err != nil {"); n != 2 {
t.Fatalf("%d of the 2 proof-cycle starts pass the discovered partition", n)
}
}
20 changes: 11 additions & 9 deletions pkg/intent/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,8 @@ func (id *IntentDiscovery) convertCertenTransactionToIntent(certenTx *accumulate
if certenTx.Partition != "" {
intent.Partition = strings.ToLower(certenTx.Partition)
}
// The BVN it was written on, for its L1-L3 proof (RB4-F46).
intent.ProofPartition = certenTx.ProofPartition
// The minor block's own time, read with the block that carried the transaction: consensus data.
intent.BlockTime = certenTx.Timestamp

Expand Down Expand Up @@ -1570,7 +1572,7 @@ func (id *IntentDiscovery) processIntent(intent *CertenIntent, blockHeight uint6
defer cancel()

// REAL L1-L3 consensus-bound chained proof (requires txHash, partition, CometBFT binding).
realProofApplicable := id.proofGenerator.HasRealProofBuilder() && intent.TransactionHash != "" && intent.Partition != ""
realProofApplicable := id.proofGenerator.HasRealProofBuilder() && intent.TransactionHash != "" && intent.ProofPartition != ""
if realProofApplicable {
// on_demand (financial) gets in-line retry to absorb DN-anchoring latency / transient
// DN-BVN RPC blips; on_cadence makes a single attempt and may fall back to a basic proof.
Expand All @@ -1579,9 +1581,9 @@ func (id *IntentDiscovery) processIntent(intent *CertenIntent, blockHeight uint6
inlineAttempts = id.config.ChainedProofInlineRetries
}
id.logger.Printf("🔗 [REAL-PROOF] Generating L1-L4 chained proof for %s (txHash=%s, partition=%s, attempts=%d)",
intent.IntentID, intent.TransactionHash[:16]+"...", intent.Partition, inlineAttempts)
intent.IntentID, intent.TransactionHash[:16]+"...", intent.ProofPartition, inlineAttempts)

cp, perr := id.buildChainedCertenProof(ctx, accountURL, intent.TransactionHash, intent.Partition, intent.IntentID, inlineAttempts)
cp, perr := id.buildChainedCertenProof(ctx, accountURL, intent.TransactionHash, intent.ProofPartition, intent.IntentID, inlineAttempts)
if perr != nil {
id.logger.Printf("⚠️ [REAL-PROOF] L1-L4 chained proof unavailable for %s: %v", intent.IntentID, perr)
} else {
Expand Down Expand Up @@ -1610,9 +1612,9 @@ func (id *IntentDiscovery) processIntent(intent *CertenIntent, blockHeight uint6
// each must be backed by the same chained proof.
if certenProof == nil {
if !realProofApplicable {
return consensus.TargetChainFailed, fmt.Errorf("intent %s (proofClass=%s): %w (realBuilder=%v txHash=%q partition=%q)",
return consensus.TargetChainFailed, fmt.Errorf("intent %s (proofClass=%s): %w (realBuilder=%v txHash=%q proof partition=%q)",
intent.IntentID, proofClass, errChainedProofTerminal,
id.proofGenerator.HasRealProofBuilder(), intent.TransactionHash, intent.Partition)
id.proofGenerator.HasRealProofBuilder(), intent.TransactionHash, intent.ProofPartition)
}
return consensus.TargetChainFailed, fmt.Errorf("intent %s (proofClass=%s): %w", intent.IntentID, proofClass, errChainedProofUnavailable)
}
Expand Down Expand Up @@ -1732,15 +1734,15 @@ func (id *IntentDiscovery) processMultiLegIntent(intent *CertenIntent, blockHeig
defer cancel()

// REAL L1-L3 consensus-bound chained proof (same fail-closed policy as single-leg).
realProofApplicable := id.proofGenerator.HasRealProofBuilder() && intent.TransactionHash != "" && intent.Partition != ""
realProofApplicable := id.proofGenerator.HasRealProofBuilder() && intent.TransactionHash != "" && intent.ProofPartition != ""
if realProofApplicable {
inlineAttempts := 1
if proofClass == "on_demand" {
inlineAttempts = id.config.ChainedProofInlineRetries
}
id.logger.Printf("🔗 [MULTI-LEG] Generating L1-L3 chained proof for %s (attempts=%d)", intent.IntentID, inlineAttempts)

cp, perr := id.buildChainedCertenProof(ctx, accountURL, intent.TransactionHash, intent.Partition, intent.IntentID, inlineAttempts)
cp, perr := id.buildChainedCertenProof(ctx, accountURL, intent.TransactionHash, intent.ProofPartition, intent.IntentID, inlineAttempts)
if perr != nil {
id.logger.Printf("⚠️ [MULTI-LEG] L1-L3 chained proof unavailable for %s: %v", intent.IntentID, perr)
} else {
Expand All @@ -1754,9 +1756,9 @@ func (id *IntentDiscovery) processMultiLegIntent(intent *CertenIntent, blockHeig
// idempotent so the requeue is replay-safe.
if certenProof == nil {
if !realProofApplicable {
return consensus.TargetChainFailed, fmt.Errorf("multi-leg intent %s (proofClass=%s): %w (realBuilder=%v txHash=%q partition=%q)",
return consensus.TargetChainFailed, fmt.Errorf("multi-leg intent %s (proofClass=%s): %w (realBuilder=%v txHash=%q proof partition=%q)",
intent.IntentID, proofClass, errChainedProofTerminal,
id.proofGenerator.HasRealProofBuilder(), intent.TransactionHash, intent.Partition)
id.proofGenerator.HasRealProofBuilder(), intent.TransactionHash, intent.ProofPartition)
}
return consensus.TargetChainFailed, fmt.Errorf("multi-leg intent %s (proofClass=%s): %w", intent.IntentID, proofClass, errChainedProofUnavailable)
}
Expand Down
39 changes: 39 additions & 0 deletions pkg/intent/proof_partition_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2026 Certen Protocol

package intent

import (
"log"
"os"
"strings"
"testing"

"github.com/certen/independant-validator/pkg/accumulate"
)

// RB4-F46: an intent is proved on the BVN it was written on. Discovery reads a Directory Network block through
// the BVN blocks it anchored and stamped every intent with "acc://dn.acme"; the L1-L3 proof refuses a non-BVN
// partition, so no intent could be proved (Phase C, 2026-09-29: "partition acc://dn.acme ... is not a BVN").
// The discovery pair (the DN block and its height) stays the batch commit pair; the BVN rides beside it.
func TestAnIntentCarriesTheBVNItWasWrittenOn(t *testing.T) {
id := &IntentDiscovery{logger: log.New(os.Stderr, "", 0)}
ci, err := id.convertCertenTransactionToIntent(&accumulate.CertenTransaction{
Hash: strings.Repeat("b2", 32), AccountURL: "acc://harbor.acme/data", BlockHeight: 9987981,
Partition: "acc://dn.acme", ProofPartition: "bvn1", ProofBlockIndex: 8270001,
IntentData: map[string]interface{}{
"intentData": map[string]interface{}{"intent_id": "f46", "proof_class": "on_demand"},
"crossChainData": map[string]interface{}{},
"governanceData": map[string]interface{}{"organizationAdi": "acc://harbor.acme"},
"replayData": map[string]interface{}{"expires_at": 1790600000},
},
})
if err != nil {
t.Fatal(err)
}
if ci.ProofPartition != "bvn1" {
t.Fatalf("the intent is proved on %q; it was written on bvn1", ci.ProofPartition)
}
if ci.Partition != "acc://dn.acme" {
t.Fatalf("the discovery partition (the batch commit pair) became %q", ci.Partition)
}
}
Loading