Skip to content
Open
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
69 changes: 69 additions & 0 deletions census/censusdb/censusdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,75 @@ func (c *CensusDB) LoadByScopedAddress(chainID uint64, address common.Address) (
return c.loadCensusRef(scopedAddressToCensusID(chainID, address), scopedAddressDBPrefix(chainID, address))
}

// LoadFromPersistentTree loads a census tree directly from the persistent KV
// storage (censusTreeDBPrefix) for the given root hash, bypassing the
// ephemeral Pebble cache. This recovers census state after a node restart
// when the Pebble tree on disk is empty but the KV-backed tree still holds
// all leaves from the original Import call.
//
// If the census is already in the in-memory cache but its tree has no root
// (i.e. the cached ref holds an empty Pebble tree), the cached tree is
// replaced with the persistent KV-backed tree.
func (c *CensusDB) LoadFromPersistentTree(root types.HexBytes) (*CensusRef, error) {
censusID := rootToCensusID(root)

// Check in-memory cache, but only return the cached ref if its tree has a
// valid root. If the cached tree is empty (happens after restart when the
// Pebble store has been wiped), fall through and reload from the KV backend.
c.mu.RLock()
if ref, exists := c.loadedCensus[censusID]; exists {
if _, ok := ref.tree.Root(); ok {
c.mu.RUnlock()
return ref, nil
}
}
c.mu.RUnlock()

// Open the persistent KV-backed tree.
treeDB := prefixeddb.NewPrefixedDatabase(c.db, censusTreeDBPrefix(censusID))
tree, err := census.NewCensusIMT(treeDB, censusHasher)
if err != nil {
return nil, fmt.Errorf("failed to open persistent census tree: %w", err)
}
treeRoot, ok := tree.Root()
if !ok {
return nil, fmt.Errorf("%w: persistent tree has no root for census root %s",
ErrCensusNotFound, root.String())
}

c.mu.Lock()
defer c.mu.Unlock()

// Re-check under write lock.
if ref, exists := c.loadedCensus[censusID]; exists {
if _, ok := ref.tree.Root(); ok {
return ref, nil
}
// Cached ref has an empty tree — replace it with the persistent tree.
ref.SetTree(tree)
ref.currentRoot = treeRoot.Bytes()
ref.LastUsed = time.Now()
return ref, nil
}

ref := &CensusRef{
ID: censusID,
HashType: censusHasherName,
LastUsed: time.Now(),
updateRootRequest: c.updateRootChan,
}
ref.currentRoot = treeRoot.Bytes()
ref.SetTree(tree)

c.loadedCensus[censusID] = ref
rk := rootKey(ref.currentRoot)
if _, exists := c.rootIndex[rk]; !exists {
c.rootIndex[rk] = censusID
}
Comment thread
p4u marked this conversation as resolved.

return ref, nil
}

// loadCensusRef loads a census reference from memory or persistent DB using a
// double‑check. It takes the censusID and the key to use.
func (c *CensusDB) loadCensusRef(censusID uuid.UUID, key types.HexBytes) (*CensusRef, error) {
Expand Down
2 changes: 1 addition & 1 deletion census/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ func queryEvents(
if err != nil {
return nil, fmt.Errorf("error creating request: %v", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set(contentTypeHeader, "application/json")
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error executing request: %v", err)
Expand Down
12 changes: 7 additions & 5 deletions census/importer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/vocdoni/davinci-node/types"
)

const testCensusURI = "https://example.invalid/dump"

func testNewCensusDB(c *qt.C) *censusdb.CensusDB {
c.Helper()
internalDB, err := metadb.New(db.TypeInMem, "")
Expand Down Expand Up @@ -73,7 +75,7 @@ func TestCensusImporter(t *testing.T) {
importer := NewCensusImporter(nil)
_, err := importer.ImportCensus(c.Context(), 0, &types.Census{
CensusOrigin: types.CensusOriginUnknown,
CensusURI: "https://example.invalid/dump",
CensusURI: testCensusURI,
CensusRoot: types.HexBytes{0x01},
}, 0)
c.Assert(err, qt.Not(qt.IsNil))
Expand Down Expand Up @@ -104,13 +106,13 @@ func TestCensusImporter(t *testing.T) {
validFn: func(string) bool { return false },
}
plugin2 := &testImporterPlugin{
validFn: func(uri string) bool { return uri == "https://example.invalid/dump" },
validFn: func(uri string) bool { return uri == testCensusURI },
}

importer := NewCensusImporter(stg, plugin1, plugin2)
census := &types.Census{
CensusOrigin: types.CensusOriginMerkleTreeOffchainStaticV1,
CensusURI: "https://example.invalid/dump",
CensusURI: testCensusURI,
CensusRoot: types.HexBytes{0xaa, 0xbb},
}

Expand All @@ -136,7 +138,7 @@ func TestCensusImporter(t *testing.T) {
importer := NewCensusImporter(stg, plugin)
_, err := importer.ImportCensus(c.Context(), 0, &types.Census{
CensusOrigin: types.CensusOriginMerkleTreeOffchainDynamicV1,
CensusURI: "https://example.invalid/dump",
CensusURI: testCensusURI,
CensusRoot: types.HexBytes{0x01},
}, 0)
c.Assert(err, qt.ErrorIs, sentinelErr)
Expand All @@ -150,7 +152,7 @@ func TestCensusImporter(t *testing.T) {
importer := NewCensusImporter(stg, plugin)
_, err := importer.ImportCensus(c.Context(), 0, &types.Census{
CensusOrigin: types.CensusOriginMerkleTreeOffchainStaticV1,
CensusURI: "https://example.invalid/dump",
CensusURI: testCensusURI,
CensusRoot: types.HexBytes{0x01},
}, 0)
c.Assert(err, qt.Not(qt.IsNil))
Expand Down
4 changes: 3 additions & 1 deletion census/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const (
UnknownJSON
)

const contentTypeHeader = "Content-Type"

// String returns the string representation of the JSONFormat.
func (format JSONFormat) String() string {
switch format {
Expand Down Expand Up @@ -127,7 +129,7 @@ func requestRawDump(ctx context.Context, targetURL string) (*http.Response, erro
// cannot be determined, it returns UnknownJSON.
func jsonReader(res *http.Response) (io.Reader, JSONFormat, error) {
// Check Content-Type header first
contentType := strings.ToLower(res.Header.Get("Content-Type"))
contentType := strings.ToLower(res.Header.Get(contentTypeHeader))

if strings.Contains(contentType, "ndjson") || strings.Contains(contentType, "jsonl") {
return res.Body, JSONL, nil
Expand Down
21 changes: 13 additions & 8 deletions census/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import (
leancensus "github.com/vocdoni/lean-imt-go/census"
)

const (
testCensusURI2 = "https://example.invalid/dump"
testContentTypeNDJSON = "application/x-ndjson"
)

type testErrReader struct {
err error
}
Expand Down Expand Up @@ -125,7 +130,7 @@ func TestRequestRawDump(t *testing.T) {
c.Assert(r.Header.Get("Accept"), qt.Equals, "application/x-ndjson, application/json;q=0.9, */*;q=0.1")
return &http.Response{
StatusCode: http.StatusOK,
Header: http.Header{"Content-Type": []string{"application/x-ndjson"}},
Header: http.Header{contentTypeHeader: []string{testContentTypeNDJSON}},
Body: io.NopCloser(strings.NewReader(`{"ok":true}` + "\n")),
Request: r,
}, nil
Expand Down Expand Up @@ -192,7 +197,7 @@ func TestJSONReader(t *testing.T) {
c.Run("ContentTypeJSONL", func(c *qt.C) {
const body = `{"a":1}` + "\n"
res := &http.Response{
Header: http.Header{"Content-Type": []string{"application/x-ndjson"}},
Header: http.Header{contentTypeHeader: []string{testContentTypeNDJSON}},
Body: io.NopCloser(strings.NewReader(body)),
}

Expand All @@ -208,7 +213,7 @@ func TestJSONReader(t *testing.T) {
c.Run("ContentTypeJSONArray", func(c *qt.C) {
const body = `[{"a":1}]`
res := &http.Response{
Header: http.Header{"Content-Type": []string{"application/json; charset=utf-8"}},
Header: http.Header{contentTypeHeader: []string{"application/json; charset=utf-8"}},
Body: io.NopCloser(strings.NewReader(body)),
}

Expand Down Expand Up @@ -352,7 +357,7 @@ func TestJSONDownloadAndImportCensus(t *testing.T) {
http.DefaultTransport = roundTripperFunc(func(r *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Header: http.Header{"Content-Type": []string{"application/json"}},
Header: http.Header{contentTypeHeader: []string{"application/json"}},
Body: &testReadCloser{
Reader: bytes.NewReader(dumpJSON),
closeErr: fmt.Errorf("close error"),
Expand Down Expand Up @@ -382,7 +387,7 @@ func TestJSONDownloadAndImportCensus(t *testing.T) {
c.Cleanup(func() { http.DefaultTransport = oldTransport })

_, err := ji.ImportCensus(c.Context(), censusDB, 0, &types.Census{
CensusURI: "https://example.invalid/dump",
CensusURI: testCensusURI2,
CensusRoot: expectedRoot,
}, 0)
c.Assert(err, qt.Not(qt.IsNil))
Expand All @@ -402,7 +407,7 @@ func TestJSONDownloadAndImportCensus(t *testing.T) {
c.Cleanup(func() { http.DefaultTransport = oldTransport })

_, err := ji.ImportCensus(c.Context(), censusDB, 0, &types.Census{
CensusURI: "https://example.invalid/dump",
CensusURI: testCensusURI2,
CensusRoot: expectedRoot,
}, 0)
c.Assert(err, qt.Not(qt.IsNil))
Expand All @@ -414,7 +419,7 @@ func TestJSONDownloadAndImportCensus(t *testing.T) {
http.DefaultTransport = roundTripperFunc(func(r *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Header: http.Header{"Content-Type": []string{"application/x-ndjson"}},
Header: http.Header{contentTypeHeader: []string{testContentTypeNDJSON}},
Body: io.NopCloser(strings.NewReader("not json\n")),
Request: r,
}, nil
Expand All @@ -435,7 +440,7 @@ func TestJSONDownloadAndImportCensus(t *testing.T) {
http.DefaultTransport = roundTripperFunc(func(r *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Header: http.Header{"Content-Type": []string{"application/json"}},
Header: http.Header{contentTypeHeader: []string{"application/json"}},
Body: io.NopCloser(bytes.NewReader(dumpJSON)),
Request: r,
}, nil
Expand Down
17 changes: 11 additions & 6 deletions census/test/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ import (
"github.com/vocdoni/davinci-node/types"
)

const (
testAccountID1 = "0xdeb8699659be5d41a0e57e179d6cb42e00b9200c"
testAccountID2 = "0xb1f05b11ba3d892edd00f2e7689779e2b8841827"
)

var (
DefaultExpectedRoot = types.HexStringToHexBytesMustUnmarshal("0x0b3600e19a4f5017dea4f91f03d8aa0dd6f4c797795e7a5aa542e81b2c5a9485")
DefaultGraphQLEvents = []TestWeightChangeEvent{
{
AccountID: "0xdeb8699659be5d41a0e57e179d6cb42e00b9200c",
AccountID: testAccountID1,
PreviousWeight: "0",
NewWeight: "1",
},
Expand All @@ -27,7 +32,7 @@ var (
NewWeight: "1",
},
{
AccountID: "0xb1f05b11ba3d892edd00f2e7689779e2b8841827",
AccountID: testAccountID2,
PreviousWeight: "0",
NewWeight: "1",
},
Expand All @@ -47,12 +52,12 @@ var (
NewWeight: "0",
},
{
AccountID: "0xdeb8699659be5d41a0e57e179d6cb42e00b9200c",
AccountID: testAccountID1,
PreviousWeight: "1",
NewWeight: "0",
},
{
AccountID: "0xb1f05b11ba3d892edd00f2e7689779e2b8841827",
AccountID: testAccountID2,
PreviousWeight: "1",
NewWeight: "2",
},
Expand All @@ -62,12 +67,12 @@ var (
NewWeight: "1",
},
{
AccountID: "0xb1f05b11ba3d892edd00f2e7689779e2b8841827",
AccountID: testAccountID2,
PreviousWeight: "2",
NewWeight: "1",
},
{
AccountID: "0xdeb8699659be5d41a0e57e179d6cb42e00b9200c",
AccountID: testAccountID1,
PreviousWeight: "0",
NewWeight: "1",
},
Expand Down
13 changes: 8 additions & 5 deletions circuits/test/statetransition/statetransition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@ import (
"github.com/vocdoni/davinci-node/util"
)

const falseString = "false"
const (
falseString = "false"
testTimeFormat = "15:04:05"
)

func TestMain(m *testing.M) {
// enable log to see nbConstraints
logger.Set(zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: "15:04:05"}).With().Timestamp().Logger())
logger.Set(zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: testTimeFormat}).With().Timestamp().Logger())
m.Run()
}

Expand All @@ -50,7 +53,7 @@ func testCircuitCompile(t *testing.T, c frontend.Circuit) {
t.Skip("skipping circuit tests...")
}
// enable log to see nbConstraints
logger.Set(zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: "15:04:05"}).With().Timestamp().Logger())
logger.Set(zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: testTimeFormat}).With().Timestamp().Logger())
if _, err := frontend.Compile(params.StateTransitionCurve.ScalarField(), r1cs.NewBuilder, c); err != nil {
panic(err)
}
Expand All @@ -69,7 +72,7 @@ func testCircuitProve(t *testing.T, circuit, assignment frontend.Circuit) {
}

func TestStateTransitionCircuit(t *testing.T) {
logger.Set(zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: "15:04:05"}).With().Timestamp().Logger())
logger.Set(zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: testTimeFormat}).With().Timestamp().Logger())
if os.Getenv("RUN_CIRCUIT_TESTS") == "" || os.Getenv("RUN_CIRCUIT_TESTS") == falseString {
t.Skip("skipping circuit tests...")
}
Expand All @@ -87,7 +90,7 @@ func TestStateTransitionCircuit(t *testing.T) {
}

func TestStateTransitionFullProvingCircuit(t *testing.T) {
logger.Set(zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: "15:04:05"}).With().Timestamp().Logger())
logger.Set(zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: testTimeFormat}).With().Timestamp().Logger())
if os.Getenv("RUN_CIRCUIT_TESTS") == "" || os.Getenv("RUN_CIRCUIT_TESTS") == falseString {
t.Skip("skipping circuit tests...")
}
Expand Down
10 changes: 6 additions & 4 deletions circuits/test/voteverifier/vote_verifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import (
"github.com/vocdoni/davinci-node/types"
)

const testTimeFormat = "15:04:05"

func TestVerifyMerkletreeVoteCircuit(t *testing.T) {
logger.Set(zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: "15:04:05"}).With().Timestamp().Logger())
logger.Set(zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: testTimeFormat}).With().Timestamp().Logger())
c := qt.New(t)
// Generate a deterministic voter account for reproducible test data.
s, err := ballottest.GenDeterministicECDSAaccountForTest(0)
Expand All @@ -45,7 +47,7 @@ func TestVerifyMerkletreeVoteCircuit(t *testing.T) {
}

func TestVerifyCSPVoteCircuit(t *testing.T) {
logger.Set(zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: "15:04:05"}).With().Timestamp().Logger())
logger.Set(zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: testTimeFormat}).With().Timestamp().Logger())
c := qt.New(t)
// Generate a deterministic voter account for reproducible test data.
s, err := ballottest.GenDeterministicECDSAaccountForTest(0)
Expand Down Expand Up @@ -81,7 +83,7 @@ func TestVerifyNoValidVoteCircuit(t *testing.T) {
}

func TestVerifyMultipleVotesCircuit(t *testing.T) {
logger.Set(zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: "15:04:05"}).With().Timestamp().Logger())
logger.Set(zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: testTimeFormat}).With().Timestamp().Logger())
if os.Getenv("RUN_CIRCUIT_TESTS") == "" || os.Getenv("RUN_CIRCUIT_TESTS") == "false" {
t.Skip("skipping circuit tests...")
}
Expand Down Expand Up @@ -109,7 +111,7 @@ func TestCompileAndPrintConstraints(t *testing.T) {
if os.Getenv("RUN_CIRCUIT_TESTS") == "" || os.Getenv("RUN_CIRCUIT_TESTS") == "false" {
t.Skip("skipping circuit tests...")
}
logger.Set(zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: "15:04:05"}).With().Timestamp().Logger())
logger.Set(zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: testTimeFormat}).With().Timestamp().Logger())
c := qt.New(t)
// generate vote verifier circuit and inputs with deterministic ProcessID
vvPlaceholder, err := voteverifier.DummyPlaceholder()
Expand Down
Loading
Loading