diff --git a/api/api_types.go b/api/api_types.go index 00c898dac..b2a0e70a2 100644 --- a/api/api_types.go +++ b/api/api_types.go @@ -34,6 +34,8 @@ type ElectionSummary struct { FinalResults bool `json:"finalResults"` Results [][]*types.BigInt `json:"result,omitempty"` ManuallyEnded bool `json:"manuallyEnded"` + FromArchive bool `json:"fromArchive"` + ChainID string `json:"chainId"` } // ElectionResults is the struct used to wrap the results of an election diff --git a/api/helpers.go b/api/helpers.go index d651d68f9..87aa03e62 100644 --- a/api/helpers.go +++ b/api/helpers.go @@ -21,15 +21,25 @@ import ( ) func (a *API) electionSummary(pi *indexertypes.Process) ElectionSummary { + startDate := pi.StartDate + if startDate.IsZero() { + startDate = a.vocinfo.HeightTime(uint64(pi.StartBlock)) + } + endDate := pi.EndDate + if endDate.IsZero() { + endDate = a.vocinfo.HeightTime(uint64(pi.EndBlock)) + } return ElectionSummary{ ElectionID: pi.ID, OrganizationID: pi.EntityID, Status: models.ProcessStatus_name[pi.Status], - StartDate: a.vocinfo.HeightTime(uint64(pi.StartBlock)), - EndDate: a.vocinfo.HeightTime(uint64(pi.EndBlock)), + StartDate: startDate, + EndDate: endDate, FinalResults: pi.FinalResults, VoteCount: pi.VoteCount, ManuallyEnded: pi.EndBlock < pi.StartBlock+pi.BlockCount, + ChainID: pi.ChainID, + FromArchive: pi.FromArchive, } } @@ -115,12 +125,8 @@ func convertKeysToCamelInner(val any) any { // encodeEVMResultsArgs encodes the arguments for the EVM mimicking the Solidity built-in abi.encode(args...) // in this case we encode the organizationId the censusRoot and the results that will be translated in the EVM // contract to the corresponding struct{address, bytes32, uint256[][]} -func encodeEVMResultsArgs(electionId common.Hash, - organizationId common.Address, - censusRoot common.Hash, - sourceContractAddress common.Address, - results [][]*types.BigInt, -) (string, error) { +func encodeEVMResultsArgs(electionId common.Hash, organizationId common.Address, censusRoot common.Hash, + sourceContractAddress common.Address, results [][]*types.BigInt) (string, error) { address, _ := abi.NewType("address", "", nil) bytes32, _ := abi.NewType("bytes32", "", nil) uint256SliceNested, _ := abi.NewType("uint256[][]", "", nil) diff --git a/service/indexer.go b/service/indexer.go index b35657c54..788a26b41 100644 --- a/service/indexer.go +++ b/service/indexer.go @@ -24,7 +24,7 @@ func (vs *VocdoniService) VochainIndexer() error { if vs.Config.Indexer.ArchiveURL != "" { log.Infow("starting archive retrieval", "path", vs.Config.Indexer.ArchiveURL) - go vs.Indexer.StartArchiveRetrival(vs.Storage, vs.Config.Indexer.ArchiveURL) + go vs.Indexer.StartArchiveRetrieval(vs.Storage, vs.Config.Indexer.ArchiveURL) } return nil diff --git a/types/consts.go b/types/consts.go index f12d64aae..f549b91f5 100644 --- a/types/consts.go +++ b/types/consts.go @@ -1,9 +1,5 @@ package types -import ( - "time" -) - func Bool(b bool) *bool { return &b } // These exported variables should be treated as constants, to be used in API @@ -14,152 +10,39 @@ var ( ) const ( - // All - // The mode defines the behaviour of the vocdoninode - // ModeMiner starts vocdoninode as a miner + // ModeMiner starts vocdoninode as a miner. ModeMiner = "miner" - // ModeSeed starts vocdoninode as a seed node + // ModeSeed starts vocdoninode as a seed node. ModeSeed = "seed" - // ModeGateway starts the vocdoninode as a gateway + // ModeGateway starts the vocdoninode as a gateway. ModeGateway = "gateway" - // ModeCensus starts the vocdoninode as a census only service + // ModeCensus starts the vocdoninode as a census only service. ModeCensus = "census" - // ProcessIDsize is the size of a process id + // ProcessIDsize is the size of a process id. ProcessIDsize = 32 - // EthereumAddressSize is the size of an ethereum address - EthereumAddressSize = 20 - - // EntityIDsize V2 legacy: in the past we used hash(addr) - // this is a temporal work around to support both - EntityIDsize = 20 - // KeyIndexSeparator is the default char used to split keys - KeyIndexSeparator = ":" - // EthereumConfirmationsThreshold is the minimum amout of blocks - // that should pass before considering a tx final - EthereumConfirmationsThreshold = 6 - - // ENS Domains - - // EntityResolverDomain default entity resolver ENS domain - EntityResolverDomain = "entities.voc.eth" - // EntityResolverStageDomain is the default entity resolver ENS domain - EntityResolverStageDomain = "entities.stg.voc.eth" - // EntityResolverDevelopmentDomain is the default entity resolver ENS domain - EntityResolverDevelopmentDomain = "entities.dev.voc.eth" - - // ProcessesDomain default process domain - ProcessesDomain = "processes.voc.eth" - // ProcessesStageDomain stage process domain - ProcessesStageDomain = "processes.stg.voc.eth" - // ProcessesDevelopmentDomain dev process domain - ProcessesDevelopmentDomain = "processes.dev.voc.eth" - - // NamespacesDomain default namespace domain - NamespacesDomain = "namespaces.voc.eth" - // NamespacesStageDomain stage namespace domain - NamespacesStageDomain = "namespaces.stg.voc.eth" - // NamespacesDevelopmentDomain dev namespace domain - NamespacesDevelopmentDomain = "namespaces.dev.voc.eth" - - // ERC20ProofsDomain default domain for erc20 proofs - ERC20ProofsDomain = "erc20.proofs.voc.eth" - // ERC20ProofsStageDomain domain for erc20 proofs stage - ERC20ProofsStageDomain = "erc20.proofs.stg.voc.eth" - // ERC20ProofsDevelopmentDomain domain for erc20 proofs dev - ERC20ProofsDevelopmentDomain = "erc20.proofs.dev.voc.eth" - - // GenesisDomain default genesis domain - GenesisDomain = "genesis.voc.eth" - // GenesisStageDomain stage genesis domain - GenesisStageDomain = "genesis.stg.voc.eth" - // GenesisDevelopmentDomain dev genesis domain - GenesisDevelopmentDomain = "genesis.dev.voc.eth" - - // ResultsDomain default results domain - ResultsDomain = "results.voc.eth" - // ResultsStageDomain stage results domain - ResultsStageDomain = "results.stg.voc.eth" - // ResultsDevelopmentDomain dev results domain - ResultsDevelopmentDomain = "results.dev.voc.eth" - // EntityMetaKey is the key of an ENS text record for the entity metadata - EntityMetaKey = "vnd.vocdoni.meta" - - // EthereumReadTimeout is the max amount of time for reading anything on - // the Ethereum network to wait until canceling it's context - EthereumReadTimeout = 1 * time.Minute - // EthereumWriteTimeout is the max amount of time for writing anything on - // the Ethereum network to wait until canceling it's context - EthereumWriteTimeout = 1 * time.Minute - // EthereumDialMaxRetry is the max number of attempts an ethereum client will - // make in order to dial to an endpoint before considering the endpoint unreachable - EthereumDialMaxRetry = 10 - - // Indexer + // EthereumAddressSize is the size of an ethereum address. + EthereumAddressSize = 20 - // IndexerLiveProcessPrefix is used for sotring temporary results on live - IndexerLiveProcessPrefix = byte(0x21) - // IndexerEntityPrefix is the prefix for the storage entity keys - IndexerEntityPrefix = byte(0x22) - // IndexerResultsPrefix is the prefix of the storage results summary keys - IndexerResultsPrefix = byte(0x24) - // IndexerProcessEndingPrefix is the prefix for keep track of the processes ending - // on a specific block - IndexerProcessEndingPrefix = byte(0x25) + // EntityIDsize is the size of an entity id (ethereum address). + EntityIDsize = EthereumAddressSize - // ArchiveURL is the default URL where the archive is retrieved from + // ArchiveURL is the default URL where the archive is retrieved from. ArchiveURL = "/ipns/k2k4r8mdn544n7f8nprwqeo27jr1v1unsu74th57s1j8mumjck7y7cbz" - // Vochain + // DefaultBlockTimeSeconds is the default block time in seconds. + DefaultBlockTimeSeconds = 12 - // PetitionSign contains the string that needs to match with the received vote type - // for petition-sign - PetitionSign = "petition-sign" - // PollVote contains the string that needs to match with the received vote type for poll-vote - PollVote = "poll-vote" - // EncryptedPoll contains the string that needs to match with the received vote type - // for encrypted-poll - EncryptedPoll = "encrypted-poll" - // SnarkVote contains the string that needs to match with the received vote type for snark-vote - SnarkVote = "snark-vote" - - // KeyKeeper - - // KeyKeeperMaxKeyIndex is the maxim number of allowed encryption keys + // KeyKeeperMaxKeyIndex is the maxim number of allowed encryption keys. KeyKeeperMaxKeyIndex = 16 - // List of transition names - - TxVote = "vote" - TxNewProcess = "newProcess" - TxCancelProcess = "cancelProcess" // legacy - TxSetProcess = "setProcess" - TxAddValidator = "addValidator" - TxRemoveValidator = "removeValidator" - TxAddProcessKeys = "addProcessKeys" - TxRevealProcessKeys = "revealProcessKeys" - - // ProcessesContractMaxProcessMode represents the max value that a uint8 can have - // with the current smart contract bitmask describing the supported process mode - ProcessesContractMaxProcessMode = 31 // ProcessesContractMaxEnvelopeType represents the max value that a uint8 can have - // with the current smart contract bitmask describing the supported envelope types + // with the current smart contract bitmask describing the supported envelope types. ProcessesContractMaxEnvelopeType = 31 - // TODO: @jordipainan this values are tricky - - // ProcessesContractMinBlockCount represents the minimum number of vochain blocks - // that a process should last - ProcessesContractMinBlockCount = 2 - - // ProcessesParamsSignatureSize represents the size of a signature on ethereum - ProcessesParamsSignatureSize = 32 - - VochainWsReadLimit = 20 << 20 // tendermint requires 20 MiB minimum - Web3WsReadLimit = 5 << 20 // go-ethereum accepts maximum 5 MiB - + // MaxURLLength is the maximum length of a URL string used in the protocol. MaxURLLength = 2083 ) diff --git a/vochain/indexer/archive.go b/vochain/indexer/archive.go index 1582149a6..c370b692b 100644 --- a/vochain/indexer/archive.go +++ b/vochain/indexer/archive.go @@ -40,7 +40,6 @@ func (idx *Indexer) ImportArchive(archive []*ArchiveProcess) ([]*ArchiveProcess, return nil, err } defer tx.Rollback() - height := idx.App.State.CurrentHeight() queries := indexerdb.New(tx) added := []*ArchiveProcess{} for _, p := range archive { @@ -61,17 +60,33 @@ func (idx *Indexer) ImportArchive(archive []*ArchiveProcess) ([]*ArchiveProcess, } else { continue } - creationTime := time.Now() - if p.StartDate != nil { - creationTime = *p.StartDate + + // For backward compatibility, we try to fetch the start/end date from multiple sources. + // If not found, we calculate them from the block count and the default block time. + startDate := p.ProcessInfo.StartDate + if startDate.IsZero() { + if p.StartDate != nil { + startDate = *p.StartDate + } else { + // Calculate startDate equal to time.Now() minus defaultBlockTime*p.ProcessInfo.BlockCount + startDate = time.Now().Add(-types.DefaultBlockTimeSeconds * time.Duration(p.ProcessInfo.BlockCount)) + } + } + endDate := p.ProcessInfo.EndDate + if endDate.IsZero() { + // Calculate endDate equal to startDate plus defaultBlockTime*p.ProcessInfo.BlockCount + endDate = startDate.Add(types.DefaultBlockTimeSeconds * time.Duration(p.ProcessInfo.BlockCount)) } + // Create and store process in the indexer database procParams := indexerdb.CreateProcessParams{ ID: nonNullBytes(p.ProcessInfo.ID), EntityID: nonNullBytes(p.ProcessInfo.EntityID), - StartBlock: int64(height), - EndBlock: int64(height + 1), - BlockCount: int64(1), + StartBlock: int64(p.ProcessInfo.StartBlock), + StartDate: startDate, + EndBlock: int64(p.ProcessInfo.EndBlock), + EndDate: endDate, + BlockCount: int64(p.ProcessInfo.BlockCount), HaveResults: p.ProcessInfo.HaveResults, FinalResults: p.ProcessInfo.FinalResults, CensusRoot: nonNullBytes(p.ProcessInfo.CensusRoot), @@ -85,13 +100,16 @@ func (idx *Indexer) ImportArchive(archive []*ArchiveProcess) ([]*ArchiveProcess, VoteOpts: indexertypes.EncodeProtoJSON(p.ProcessInfo.VoteOpts), PrivateKeys: indexertypes.EncodeJSON(p.ProcessInfo.PrivateKeys), PublicKeys: indexertypes.EncodeJSON(p.ProcessInfo.PublicKeys), - CreationTime: creationTime, + CreationTime: time.Now(), SourceBlockHeight: int64(p.ProcessInfo.SourceBlockHeight), SourceNetworkID: int64(models.SourceNetworkId_value[p.ProcessInfo.SourceNetworkId]), Metadata: p.ProcessInfo.Metadata, ResultsVotes: indexertypes.EncodeJSON(p.Results.Votes), VoteCount: int64(p.ProcessInfo.VoteCount), + ChainID: p.ChainID, + FromArchive: true, } + if _, err := queries.CreateProcess(context.TODO(), procParams); err != nil { return nil, fmt.Errorf("create archive process: %w", err) } @@ -100,9 +118,9 @@ func (idx *Indexer) ImportArchive(archive []*ArchiveProcess) ([]*ArchiveProcess, return added, tx.Commit() } -// StartArchiveRetrival starts the archive retrieval process. It is a blocking function that runs continuously. +// StartArchiveRetrieval starts the archive retrieval process. It is a blocking function that runs continuously. // Retrieves the archive directory from the storage and imports the processes into the indexer database. -func (idx *Indexer) StartArchiveRetrival(storage data.Storage, archiveURL string) { +func (idx *Indexer) StartArchiveRetrieval(storage data.Storage, archiveURL string) { for { ctx, cancel := context.WithTimeout(context.Background(), timeoutArchiveRetrieval) dirMap, err := storage.RetrieveDir(ctx, archiveURL, marxArchiveFileSize) diff --git a/vochain/indexer/archive_test.go b/vochain/indexer/archive_test.go index 495480ac7..f37559d21 100644 --- a/vochain/indexer/archive_test.go +++ b/vochain/indexer/archive_test.go @@ -27,9 +27,13 @@ func TestImportArchive(t *testing.T) { qt.Assert(t, process1.Results().Votes[0][0].MathBigInt().Int64(), qt.Equals, int64(342)) qt.Assert(t, process1.Results().Votes[0][1].MathBigInt().Int64(), qt.Equals, int64(365)) qt.Assert(t, process1.Results().Votes[0][2].MathBigInt().Int64(), qt.Equals, int64(21)) - // TODO: qt.Assert(t, process1.Results().Weight.MathBigInt().Int64(), qt.Equals, int64(342+365+21)) + qt.Assert(t, process1.StartDate, qt.DeepEquals, *archiveProcess1.StartDate) + // check that endDate is set after startDate + qt.Assert(t, process1.EndDate.After(process1.StartDate), qt.Equals, true) } +// This is an old archive process format, we check backwards compatibility. +// At some point we should update this format to the new one. var testArchiveProcess1 = ` { "chainId": "vocdoni-stage-8", diff --git a/vochain/indexer/db/models.go b/vochain/indexer/db/models.go index 854d85f28..0cdce44bf 100644 --- a/vochain/indexer/db/models.go +++ b/vochain/indexer/db/models.go @@ -27,8 +27,11 @@ type Process struct { EntityID types.EntityID StartBlock int64 EndBlock int64 + StartDate time.Time + EndDate time.Time BlockCount int64 VoteCount int64 + ChainID string HaveResults bool FinalResults bool ResultsVotes string @@ -50,6 +53,7 @@ type Process struct { CreationTime time.Time SourceBlockHeight int64 SourceNetworkID int64 + FromArchive bool } type TokenFee struct { diff --git a/vochain/indexer/db/processes.sql.go b/vochain/indexer/db/processes.sql.go index 672433fcf..044210c7a 100644 --- a/vochain/indexer/db/processes.sql.go +++ b/vochain/indexer/db/processes.sql.go @@ -15,25 +15,27 @@ import ( const createProcess = `-- name: CreateProcess :execresult INSERT INTO processes ( - id, entity_id, start_block, end_block, block_count, - vote_count, have_results, final_results, census_root, + id, entity_id, start_block, end_block, start_date, end_date, + block_count, vote_count, have_results, final_results, census_root, max_census_size, census_uri, metadata, census_origin, status, namespace, envelope, mode, vote_opts, private_keys, public_keys, question_index, creation_time, source_block_height, source_network_id, + from_archive, chain_id, results_votes, results_weight, results_block_height ) VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, - ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, + ?, ?, ?, '"0"', 0 ) @@ -44,6 +46,8 @@ type CreateProcessParams struct { EntityID types.EntityID StartBlock int64 EndBlock int64 + StartDate time.Time + EndDate time.Time BlockCount int64 VoteCount int64 HaveResults bool @@ -64,6 +68,8 @@ type CreateProcessParams struct { CreationTime time.Time SourceBlockHeight int64 SourceNetworkID int64 + FromArchive bool + ChainID string ResultsVotes string } @@ -73,6 +79,8 @@ func (q *Queries) CreateProcess(ctx context.Context, arg CreateProcessParams) (s arg.EntityID, arg.StartBlock, arg.EndBlock, + arg.StartDate, + arg.EndDate, arg.BlockCount, arg.VoteCount, arg.HaveResults, @@ -93,6 +101,8 @@ func (q *Queries) CreateProcess(ctx context.Context, arg CreateProcessParams) (s arg.CreationTime, arg.SourceBlockHeight, arg.SourceNetworkID, + arg.FromArchive, + arg.ChainID, arg.ResultsVotes, ) } @@ -109,7 +119,7 @@ func (q *Queries) GetEntityCount(ctx context.Context) (int64, error) { } const getProcess = `-- name: GetProcess :one -SELECT id, entity_id, start_block, end_block, block_count, vote_count, have_results, final_results, results_votes, results_weight, results_block_height, census_root, max_census_size, census_uri, metadata, census_origin, status, namespace, envelope, mode, vote_opts, private_keys, public_keys, question_index, creation_time, source_block_height, source_network_id FROM processes +SELECT id, entity_id, start_block, end_block, start_date, end_date, block_count, vote_count, chain_id, have_results, final_results, results_votes, results_weight, results_block_height, census_root, max_census_size, census_uri, metadata, census_origin, status, namespace, envelope, mode, vote_opts, private_keys, public_keys, question_index, creation_time, source_block_height, source_network_id, from_archive FROM processes WHERE id = ? GROUP BY id LIMIT 1 @@ -123,8 +133,11 @@ func (q *Queries) GetProcess(ctx context.Context, id types.ProcessID) (Process, &i.EntityID, &i.StartBlock, &i.EndBlock, + &i.StartDate, + &i.EndDate, &i.BlockCount, &i.VoteCount, + &i.ChainID, &i.HaveResults, &i.FinalResults, &i.ResultsVotes, @@ -146,6 +159,7 @@ func (q *Queries) GetProcess(ctx context.Context, id types.ProcessID) (Process, &i.CreationTime, &i.SourceBlockHeight, &i.SourceNetworkID, + &i.FromArchive, ) return i, err } @@ -306,12 +320,18 @@ func (q *Queries) SearchProcesses(ctx context.Context, arg SearchProcessesParams const setProcessResultsCancelled = `-- name: SetProcessResultsCancelled :execresult UPDATE processes -SET have_results = FALSE, final_results = TRUE -WHERE id = ?1 +SET have_results = FALSE, final_results = TRUE, + end_date = ?1 +WHERE id = ?2 ` -func (q *Queries) SetProcessResultsCancelled(ctx context.Context, id types.ProcessID) (sql.Result, error) { - return q.exec(ctx, q.setProcessResultsCancelledStmt, setProcessResultsCancelled, id) +type SetProcessResultsCancelledParams struct { + EndDate time.Time + ID types.ProcessID +} + +func (q *Queries) SetProcessResultsCancelled(ctx context.Context, arg SetProcessResultsCancelledParams) (sql.Result, error) { + return q.exec(ctx, q.setProcessResultsCancelledStmt, setProcessResultsCancelled, arg.EndDate, arg.ID) } const setProcessResultsReady = `-- name: SetProcessResultsReady :execresult @@ -319,14 +339,16 @@ UPDATE processes SET have_results = TRUE, final_results = TRUE, results_votes = ?1, results_weight = ?2, - results_block_height = ?3 -WHERE id = ?4 + results_block_height = ?3, + end_date = ?4 +WHERE id = ?5 ` type SetProcessResultsReadyParams struct { Votes string Weight string BlockHeight int64 + EndDate time.Time ID types.ProcessID } @@ -335,6 +357,7 @@ func (q *Queries) SetProcessResultsReady(ctx context.Context, arg SetProcessResu arg.Votes, arg.Weight, arg.BlockHeight, + arg.EndDate, arg.ID, ) } @@ -356,17 +379,19 @@ func (q *Queries) SetProcessVoteCount(ctx context.Context, arg SetProcessVoteCou const updateProcessEndBlock = `-- name: UpdateProcessEndBlock :execresult UPDATE processes -SET end_block = ?1 -WHERE id = ?2 +SET end_block = ?1, + end_date = ?2 +WHERE id = ?3 ` type UpdateProcessEndBlockParams struct { EndBlock int64 + EndDate time.Time ID types.ProcessID } func (q *Queries) UpdateProcessEndBlock(ctx context.Context, arg UpdateProcessEndBlockParams) (sql.Result, error) { - return q.exec(ctx, q.updateProcessEndBlockStmt, updateProcessEndBlock, arg.EndBlock, arg.ID) + return q.exec(ctx, q.updateProcessEndBlockStmt, updateProcessEndBlock, arg.EndBlock, arg.EndDate, arg.ID) } const updateProcessFromState = `-- name: UpdateProcessFromState :execresult @@ -378,8 +403,9 @@ SET census_root = ?1, private_keys = ?3, public_keys = ?4, metadata = ?5, - status = ?6 -WHERE id = ?7 + status = ?6, + start_date = ?7 +WHERE id = ?8 ` type UpdateProcessFromStateParams struct { @@ -389,6 +415,7 @@ type UpdateProcessFromStateParams struct { PublicKeys string Metadata string Status int64 + StartDate time.Time ID types.ProcessID } @@ -400,6 +427,7 @@ func (q *Queries) UpdateProcessFromState(ctx context.Context, arg UpdateProcessF arg.PublicKeys, arg.Metadata, arg.Status, + arg.StartDate, arg.ID, ) } diff --git a/vochain/indexer/indexer.go b/vochain/indexer/indexer.go index ae85533d5..7554f6d4a 100644 --- a/vochain/indexer/indexer.go +++ b/vochain/indexer/indexer.go @@ -541,8 +541,7 @@ func (idx *Indexer) OnRevealKeys(pid []byte, _ string, _ int32) { } // OnProcessResults verifies the results for a process and appends it to blockUpdateProcs -func (idx *Indexer) OnProcessResults(pid []byte, _ *models.ProcessResult, - _ int32) { +func (idx *Indexer) OnProcessResults(pid []byte, _ *models.ProcessResult, _ int32) { idx.blockMu.Lock() defer idx.blockMu.Unlock() idx.blockUpdateProcs[string(pid)] = true diff --git a/vochain/indexer/indexertypes/types.go b/vochain/indexer/indexertypes/types.go index 99f9bc6d3..7766f8ca9 100644 --- a/vochain/indexer/indexertypes/types.go +++ b/vochain/indexer/indexertypes/types.go @@ -21,6 +21,8 @@ type Process struct { EntityID types.HexBytes `json:"entityId"` StartBlock uint32 `json:"startBlock"` EndBlock uint32 `json:"endBlock"` + StartDate time.Time `json:"startDate,omitempty"` + EndDate time.Time `json:"endDate,omitempty"` BlockCount uint32 `json:"blockCount"` VoteCount uint64 `json:"voteCount"` CensusRoot types.HexBytes `json:"censusRoot"` @@ -39,6 +41,8 @@ type Process struct { SourceBlockHeight uint64 `json:"sourceBlockHeight"` SourceNetworkId string `json:"sourceNetworkId"` // string form of the enum to be user friendly MaxCensusSize uint64 `json:"maxCensusSize"` + FromArchive bool `json:"fromArchive,omitempty"` + ChainID string `json:"chainId,omitempty"` PrivateKeys json.RawMessage `json:"-"` // json array PublicKeys json.RawMessage `json:"-"` // json array @@ -65,6 +69,8 @@ func ProcessFromDB(dbproc *indexerdb.Process) *Process { EntityID: nonEmptyBytes(dbproc.EntityID), StartBlock: uint32(dbproc.StartBlock), EndBlock: uint32(dbproc.EndBlock), + StartDate: dbproc.StartDate, + EndDate: dbproc.EndDate, BlockCount: uint32(dbproc.BlockCount), HaveResults: dbproc.HaveResults, FinalResults: dbproc.FinalResults, @@ -80,6 +86,8 @@ func ProcessFromDB(dbproc *indexerdb.Process) *Process { CreationTime: dbproc.CreationTime, SourceBlockHeight: uint64(dbproc.SourceBlockHeight), Metadata: dbproc.Metadata, + ChainID: dbproc.ChainID, + FromArchive: dbproc.FromArchive, PrivateKeys: json.RawMessage(dbproc.PrivateKeys), PublicKeys: json.RawMessage(dbproc.PublicKeys), diff --git a/vochain/indexer/migrations/0001_create_table_processes.sql b/vochain/indexer/migrations/0001_create_table_processes.sql index 9fc267019..c0387a434 100644 --- a/vochain/indexer/migrations/0001_create_table_processes.sql +++ b/vochain/indexer/migrations/0001_create_table_processes.sql @@ -4,8 +4,11 @@ CREATE TABLE processes ( entity_id BLOB NOT NULL, start_block INTEGER NOT NULL, end_block INTEGER NOT NULL, + start_date DATETIME NOT NULL, + end_date DATETIME NOT NULL, block_count INTEGER NOT NULL, vote_count INTEGER NOT NULL, + chain_id TEXT NOT NULL, have_results BOOLEAN NOT NULL, final_results BOOLEAN NOT NULL, @@ -31,7 +34,8 @@ CREATE TABLE processes ( question_index INTEGER NOT NULL, creation_time DATETIME NOT NULL, source_block_height INTEGER NOT NULL, - source_network_id INTEGER NOT NULL + source_network_id INTEGER NOT NULL, + from_archive BOOLEAN NOT NULL ); CREATE INDEX index_processes_entity_id diff --git a/vochain/indexer/process.go b/vochain/indexer/process.go index 1ca8146df..870d9a8a7 100644 --- a/vochain/indexer/process.go +++ b/vochain/indexer/process.go @@ -182,6 +182,8 @@ func (idx *Indexer) newEmptyProcess(pid []byte) error { SourceNetworkID: int64(p.SourceNetworkId), Metadata: p.GetMetadata(), ResultsVotes: indexertypes.EncodeJSON(results.NewEmptyVotes(options)), + ChainID: idx.App.ChainID(), + FromArchive: false, } idx.blockMu.Lock() @@ -206,6 +208,21 @@ func (idx *Indexer) updateProcess(ctx context.Context, queries *indexerdb.Querie return err } + // We need to use the time of start/end from the block header, as we might be syncing the blockchain + currentBlockTime := func() time.Time { + t := idx.App.TimestampFromBlock(int64(idx.App.Height())) + if t == nil { + return time.Now() + } + return *t + } + + // Update start date with the block time if the process starts on this block + var startDate time.Time + if idx.App.Height() == p.StartBlock { + startDate = currentBlockTime() + } + // Update the process in the indexer database if _, err := queries.UpdateProcessFromState(ctx, indexerdb.UpdateProcessFromStateParams{ ID: pid, @@ -215,6 +232,7 @@ func (idx *Indexer) updateProcess(ctx context.Context, queries *indexerdb.Querie PublicKeys: indexertypes.EncodeJSON(p.EncryptionPublicKeys), Metadata: p.GetMetadata(), Status: int64(p.Status), + StartDate: startDate, }); err != nil { return err } @@ -225,6 +243,7 @@ func (idx *Indexer) updateProcess(ctx context.Context, queries *indexerdb.Querie if _, err := queries.UpdateProcessEndBlock(ctx, indexerdb.UpdateProcessEndBlockParams{ ID: pid, EndBlock: int64(idx.App.Height()), + EndDate: currentBlockTime(), }); err != nil { return err } @@ -241,7 +260,11 @@ func (idx *Indexer) updateProcess(ctx context.Context, queries *indexerdb.Querie // If the process is in CANCELED status, and it was not in CANCELED status before, then remove the results if models.ProcessStatus(previousStatus) != models.ProcessStatus_CANCELED && p.Status == models.ProcessStatus_CANCELED { - if _, err := queries.SetProcessResultsCancelled(ctx, pid); err != nil { + if _, err := queries.SetProcessResultsCancelled(ctx, + indexerdb.SetProcessResultsCancelledParams{ + EndDate: currentBlockTime(), + ID: pid, + }); err != nil { return err } } diff --git a/vochain/indexer/queries/processes.sql b/vochain/indexer/queries/processes.sql index abe5a99e0..d7cb475d2 100644 --- a/vochain/indexer/queries/processes.sql +++ b/vochain/indexer/queries/processes.sql @@ -1,24 +1,26 @@ -- name: CreateProcess :execresult INSERT INTO processes ( - id, entity_id, start_block, end_block, block_count, - vote_count, have_results, final_results, census_root, + id, entity_id, start_block, end_block, start_date, end_date, + block_count, vote_count, have_results, final_results, census_root, max_census_size, census_uri, metadata, census_origin, status, namespace, envelope, mode, vote_opts, private_keys, public_keys, question_index, creation_time, source_block_height, source_network_id, + from_archive, chain_id, results_votes, results_weight, results_block_height ) VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, - ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, + ?, ?, ?, '"0"', 0 ); @@ -50,7 +52,8 @@ SET census_root = sqlc.arg(census_root), private_keys = sqlc.arg(private_keys), public_keys = sqlc.arg(public_keys), metadata = sqlc.arg(metadata), - status = sqlc.arg(status) + status = sqlc.arg(status), + start_date = sqlc.arg(start_date) WHERE id = sqlc.arg(id); -- name: GetProcessStatus :one @@ -70,12 +73,14 @@ UPDATE processes SET have_results = TRUE, final_results = TRUE, results_votes = sqlc.arg(votes), results_weight = sqlc.arg(weight), - results_block_height = sqlc.arg(block_height) + results_block_height = sqlc.arg(block_height), + end_date = sqlc.arg(end_date) WHERE id = sqlc.arg(id); -- name: SetProcessResultsCancelled :execresult UPDATE processes -SET have_results = FALSE, final_results = TRUE +SET have_results = FALSE, final_results = TRUE, + end_date = sqlc.arg(end_date) WHERE id = sqlc.arg(id); -- name: SetProcessVoteCount :execresult @@ -112,5 +117,6 @@ WHERE id = sqlc.arg(id); -- name: UpdateProcessEndBlock :execresult UPDATE processes -SET end_block = sqlc.arg(end_block) +SET end_block = sqlc.arg(end_block), + end_date = sqlc.arg(end_date) WHERE id = sqlc.arg(id); diff --git a/vochain/vochaininfo/vochaininfo.go b/vochain/vochaininfo/vochaininfo.go index a266a736c..5e63bd757 100644 --- a/vochain/vochaininfo/vochaininfo.go +++ b/vochain/vochaininfo/vochaininfo.go @@ -10,6 +10,7 @@ import ( "github.com/VictoriaMetrics/metrics" coretypes "github.com/cometbft/cometbft/rpc/core/types" "go.vocdoni.io/dvote/log" + "go.vocdoni.io/dvote/types" "go.vocdoni.io/dvote/vochain" "go.vocdoni.io/dvote/vochain/state" ) @@ -68,14 +69,17 @@ func (vi *VochainInfo) updateCounters() { mempoolSize.Set(uint64(vi.vnode.MempoolSize())) } -// Height returns the current number of blocks of the blockchain +// Height returns the current number of blocks of the blockchain. func (vi *VochainInfo) Height() uint64 { return height.Get() } // BlockTimes returns the average block time in milliseconds for 1, 10, 60, 360 and 1440 minutes. -// Value 0 means there is not yet an average +// Value 0 means there is not yet an average. func (vi *VochainInfo) BlockTimes() *[5]uint64 { + if vi.vnode.IsSynchronizing() { + return &[5]uint64{types.DefaultBlockTimeSeconds, 0, 0, 0, 0} + } vi.lock.RLock() defer vi.lock.RUnlock() return &[5]uint64{vi.avg1, vi.avg10, vi.avg60, vi.avg360, vi.avg1440}