Skip to content

Commit

Permalink
vochain: introduce timestamp based elections
Browse files Browse the repository at this point in the history
Elections can be created using UNIX timestamps
instead of block number. The timestamp is taken
from the consensus block header.

Signed-off-by: p4u <pau@dabax.net>
  • Loading branch information
p4u committed Jan 5, 2024
1 parent 6eb0e36 commit d651a94
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 28 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a
github.com/vocdoni/storage-proofs-eth-go v0.1.6
go.mongodb.org/mongo-driver v1.12.1
go.vocdoni.io/proto v1.15.4-0.20231023165811-02adcc48142a
go.vocdoni.io/proto v1.15.5-0.20240105001622-a1cf91555159
golang.org/x/crypto v0.15.0
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
golang.org/x/net v0.18.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1709,8 +1709,8 @@ go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
go.vocdoni.io/proto v1.15.4-0.20231023165811-02adcc48142a h1:88Dg0JNhT9004TuZoHIX44zkaHkInKgBgBaA0S12cYY=
go.vocdoni.io/proto v1.15.4-0.20231023165811-02adcc48142a/go.mod h1:oi/WtiBFJ6QwNDv2aUQYwOnUKzYuS/fBqXF8xDNwcGo=
go.vocdoni.io/proto v1.15.5-0.20240105001622-a1cf91555159 h1:knuU/2ffXm7EYjeFL6LPestB3Pk9JAWyWqeAW1v3rnY=
go.vocdoni.io/proto v1.15.5-0.20240105001622-a1cf91555159/go.mod h1:oi/WtiBFJ6QwNDv2aUQYwOnUKzYuS/fBqXF8xDNwcGo=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg=
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
Expand Down
6 changes: 6 additions & 0 deletions util/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/url"
"path"
"strings"
"time"
)

func TrimHex(s string) string {
Expand Down Expand Up @@ -67,3 +68,8 @@ func BuildURL(parts ...string) (string, error) {
base.Path = fullPath
return base.String(), nil
}

// TimestampToTime converts a uint32 timestamp to a time.Time object.
func TimestampToTime(timestamp uint32) time.Time {
return time.Unix(int64(timestamp), 0)
}
7 changes: 7 additions & 0 deletions vochain/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ func NewBaseApplication(vochainCfg *config.VochainCfg) (*BaseApplication, error)
if err != nil {
return nil, fmt.Errorf("cannot create state: (%v)", err)
}
// Create the IST controller for internal state transitions
istc := ist.NewISTC(state)

// Create the transaction handler for checking and processing transactions
Expand All @@ -140,10 +141,12 @@ func NewBaseApplication(vochainCfg *config.VochainCfg) (*BaseApplication, error)
filepath.Join(vochainCfg.DataDir, "txHandler"),
)

// Initialize the zk circuit
if err := circuit.Init(); err != nil {
return nil, fmt.Errorf("cannot load zk circuit: %w", err)
}

// Initialize the LRU cache for blocks
blockCache, err := lru.New[int64, *tmtypes.Block](32)
if err != nil {
return nil, err
Expand Down Expand Up @@ -262,7 +265,11 @@ func (app *BaseApplication) beginBlock(t time.Time, height uint32) {
}
app.State.Rollback()
app.startBlockTimestamp.Store(t.Unix())
if err := app.State.SetTimestamp(uint32(t.Unix())); err != nil {
log.Fatalf("failed to set timestamp: %w", err)
}
app.State.SetHeight(height)

err := app.SetZkCircuit()
if err != nil {
log.Fatalf("failed to set ZkCircuit: %w", err)
Expand Down
4 changes: 4 additions & 0 deletions vochain/apptest.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ func TestBaseApplicationWithChainID(tb testing.TB, chainID string) *BaseApplicat
tb.Error(err)
}
})
// Set the initial timestamp
if err := app.State.SetTimestamp(uint32(time.Now().Unix())); err != nil {
tb.Fatal(err)
}
return app
}

Expand Down
3 changes: 3 additions & 0 deletions vochain/state/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"go.vocdoni.io/dvote/statedb"
"go.vocdoni.io/dvote/tree/arbo"
"go.vocdoni.io/dvote/types"
"go.vocdoni.io/dvote/util"
"go.vocdoni.io/proto/build/go/models"
"google.golang.org/protobuf/proto"
)
Expand Down Expand Up @@ -41,6 +42,8 @@ func (v *State) AddProcess(p *models.Process) error {
"entityId", fmt.Sprintf("%x", p.EntityId),
"startBlock", p.StartBlock,
"endBlock", p.BlockCount+p.StartBlock,
"startTime", util.TimestampToTime(p.StartTime).String(),
"endTime", util.TimestampToTime(p.StartTime+p.Duration).String(),
"mode", p.Mode,
"envelopeType", p.EnvelopeType,
"voteOptions", p.VoteOptions,
Expand Down
38 changes: 37 additions & 1 deletion vochain/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package state

import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"os"
Expand Down Expand Up @@ -33,9 +34,18 @@ const (
storageDirectory = "vcstate"
// snapshotsDirectory is the final directory name where the snapshots are stored.
snapshotsDirectory = "snapshots"

// timestampKey is the key used to store the timestamp of the last block.
timestampKey = "timestamp"
)

var BurnAddress = common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff")
var (
// BurnAddress is the address where tokens are sent when they are burned.
BurnAddress = common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff")

// ErrTimestampNotFound is returned when the timestamp is not found on the blockchain state.
ErrTimestampNotFound = fmt.Errorf("timestamp not found")
)

// _________________________ CENSUS ORIGINS __________________________

Expand Down Expand Up @@ -420,6 +430,32 @@ func (v *State) SetHeight(height uint32) {
v.currentHeight.Store(height)
}

// SetTimestamp sets the timestamp for the current (not committed) block.
func (v *State) SetTimestamp(timestamp uint32) error {
v.tx.Lock()
defer v.tx.Unlock()
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, timestamp)
return v.tx.DeepSet([]byte(timestampKey), b, StateTreeCfg(TreeExtra))
}

// Timestamp returns the timestamp for the current or committed block.
func (v *State) Timestamp(committed bool) (uint32, error) {
if !committed {
v.tx.RLock()
defer v.tx.RUnlock()
}
extraTree, err := v.mainTreeViewer(committed).SubTree(StateTreeCfg(TreeExtra))
if err != nil {
return 0, err
}
var b []byte
if b, err = extraTree.Get([]byte(timestampKey)); err != nil {
return 0, ErrTimestampNotFound
}
return binary.LittleEndian.Uint32(b), nil
}

// CommittedHash returns the hash of the last committed vochain StateDB
func (v *State) CommittedHash() []byte {
hash, err := v.mainTreeViewer(true).Root()
Expand Down
41 changes: 30 additions & 11 deletions vochain/transaction/election_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,36 @@ func (t *TransactionHandler) NewProcessTxCheck(vtx *vochaintx.Tx) (*models.Proce
if vtx.Signature == nil || tx == nil || vtx.SignedBody == nil {
return nil, ethereum.Address{}, fmt.Errorf("missing vtx.Signature or new process transaction")
}
// start and block count sanity check
// if startBlock is zero or one, the process will be enabled on the next block
if tx.Process.StartBlock == 0 || tx.Process.StartBlock == 1 {
tx.Process.StartBlock = t.state.CurrentHeight() + 1
} else if tx.Process.StartBlock < t.state.CurrentHeight() {
return nil, ethereum.Address{}, fmt.Errorf(
"cannot add process with start block lower than or equal to the current height")
}
if tx.Process.BlockCount <= 0 {
return nil, ethereum.Address{}, fmt.Errorf(
"cannot add process with duration lower than or equal to the current height")

// time based process (duration and start time)
if tx.Process.Duration > 0 {
currentTimestamp, err := t.state.Timestamp(false)
if err != nil {
return nil, ethereum.Address{}, fmt.Errorf("cannot get current timestamp: %w", err)
}
// if start time is zero or one, the process will be enabled on the next block
if tx.Process.StartTime == 0 {
tx.Process.StartTime = currentTimestamp
}
if tx.Process.StartTime < currentTimestamp {
return nil, ethereum.Address{}, fmt.Errorf("cannot add process with start time lower than the current timestamp")
}
if tx.Process.StartTime+tx.Process.Duration < currentTimestamp {
return nil, ethereum.Address{}, fmt.Errorf("cannot add process with duration lower than the current timestamp")
}
} else {
// block based process (block count and start block)
// TODO: remove due deprecation
if tx.Process.StartBlock == 0 || tx.Process.StartBlock == 1 {
tx.Process.StartBlock = t.state.CurrentHeight() + 1
} else if tx.Process.StartBlock < t.state.CurrentHeight() {
return nil, ethereum.Address{}, fmt.Errorf(
"cannot add process with start block lower than or equal to the current height")
}
if tx.Process.BlockCount <= 0 {
return nil, ethereum.Address{}, fmt.Errorf(
"cannot add process with duration lower than or equal to the current height")
}
}

// check MaxCensusSize is properly set and within the allowed range
Expand Down
46 changes: 33 additions & 13 deletions vochain/transaction/vote_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,38 @@ func (t *TransactionHandler) VoteTxCheck(vtx *vochaintx.Tx, forCommit bool) (*vs
return nil, fmt.Errorf("process %x malformed", voteEnvelope.ProcessId)
}

// Get the current height of the blockchain
// Get the current height and timestamp from the blockchain state
height := t.state.CurrentHeight()
currentTime, err := t.state.Timestamp(false)
if err != nil {
return nil, fmt.Errorf("cannot get current time: %w", err)
}

// Calculate the end block of the process
endBlock := process.StartBlock + process.BlockCount

// Check that the current height is within the bounds of the process
if height < process.StartBlock {
return nil, fmt.Errorf(
"process %x starts at height %d, current height is %d",
voteEnvelope.ProcessId, process.StartBlock, height)
} else if height > endBlock {
return nil, fmt.Errorf(
"process %x finished at height %d, current height is %d",
voteEnvelope.ProcessId, endBlock, height)
// Check the process accepts votes by height or timestamp window
if process.Duration > 0 { // Timestamp based processes
endTime := process.StartTime + process.Duration
// Check that the current time is within the bounds of the process
if currentTime < process.StartTime {
return nil, fmt.Errorf(
"process %x starts at time %d, current time is %d",
voteEnvelope.ProcessId, process.StartTime, currentTime)
} else if currentTime > endTime {
return nil, fmt.Errorf(
"process %x finished at time %d, current time is %d",
voteEnvelope.ProcessId, endTime, currentTime)
}
} else { // Block count based processes
endBlock := process.StartBlock + process.BlockCount
// Check that the current height is within the bounds of the process
if height < process.StartBlock {
return nil, fmt.Errorf(
"process %x starts at height %d, current height is %d",
voteEnvelope.ProcessId, process.StartBlock, height)
} else if height > endBlock {
return nil, fmt.Errorf(
"process %x finished at height %d, current height is %d",
voteEnvelope.ProcessId, endBlock, height)
}
}

// Check that the process is in the READY state
Expand Down Expand Up @@ -166,6 +183,8 @@ func (t *TransactionHandler) VoteTxCheck(vtx *vochaintx.Tx, forCommit bool) (*vs
log.Debugw("new vote",
"type", "zkSNARK",
"weight", vote.Weight,
"timestamp", currentTime,
"height", height,
"nullifier", fmt.Sprintf("%x", vote.Nullifier),
"electionID", fmt.Sprintf("%x", voteEnvelope.ProcessId),
)
Expand All @@ -184,6 +203,7 @@ func (t *TransactionHandler) VoteTxCheck(vtx *vochaintx.Tx, forCommit bool) (*vs
"nullifier", fmt.Sprintf("%x", vote.Nullifier),
"address", addr.Hex(),
"electionID", fmt.Sprintf("%x", voteEnvelope.ProcessId),
"timestamp", currentTime,
"height", height)

// Verify the proof
Expand Down

0 comments on commit d651a94

Please sign in to comment.