Skip to content

Commit

Permalink
vochain fork: refactor ZkCircuits handling
Browse files Browse the repository at this point in the history
* circuit/config: rename `dev` -> `v0.0.1` and add voceremony as `v1.0.0`
* circuit/config: rename `tag` concept into `version`
* circuit/config: now ZkCircuitConfig has Version field, drop app.circuitConfigTag
* TransactionHandler: unexport zkCircuit field, expose over ZkCircuit methods that implement sync.Mutex
* api: /chain/info now returns circuitVersion (instead of misspelt cicuitConfigurationTag)
* apiclient: simplify NewHTTPclient, fetching /chain/info/circuit endpoint
* vochain/app.go: SetZkCircuit during beginBlock
* add config/forks.go
* circuit: add DownloadZkCircuits funcs
  * DownloadZkCircuitsForChainID
  * DownloadDefaultZkCircuit

NewBaseApplication now calls circuit.DownloadDefaultZkCircuit
instead of transactionHandler.LoadZkCircuit

and newTendermint now calls circuit.DownloadZkCircuitsForChainID
  • Loading branch information
altergui committed Nov 28, 2023
1 parent fe5d2b5 commit f7dd973
Show file tree
Hide file tree
Showing 14 changed files with 168 additions and 104 deletions.
28 changes: 14 additions & 14 deletions api/api_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,20 +200,20 @@ type GenericTransactionWithInfo struct {
}

type ChainInfo struct {
ID string `json:"chainId" example:"azeno"`
BlockTime [5]uint64 `json:"blockTime" example:"12000,11580,11000,11100,11100"`
ElectionCount uint64 `json:"electionCount" example:"120"`
OrganizationCount uint64 `json:"organizationCount" example:"20"`
GenesisTime time.Time `json:"genesisTime" format:"date-time" example:"2022-11-17T18:00:57.379551614Z"`
Height uint32 `json:"height" example:"5467"`
Syncing bool `json:"syncing" example:"true"`
Timestamp int64 `json:"blockTimestamp" swaggertype:"string" format:"date-time" example:"2022-11-17T18:00:57.379551614Z"`
TransactionCount uint64 `json:"transactionCount" example:"554"`
ValidatorCount uint32 `json:"validatorCount" example:"5"`
VoteCount uint64 `json:"voteCount" example:"432"`
CircuitConfigurationTag string `json:"cicuitConfigurationTag" example:"dev"`
MaxCensusSize uint64 `json:"maxCensusSize" example:"50000"`
NetworkCapacity uint64 `json:"networkCapacity" example:"2000"`
ID string `json:"chainId" example:"azeno"`
BlockTime [5]uint64 `json:"blockTime" example:"12000,11580,11000,11100,11100"`
ElectionCount uint64 `json:"electionCount" example:"120"`
OrganizationCount uint64 `json:"organizationCount" example:"20"`
GenesisTime time.Time `json:"genesisTime" format:"date-time" example:"2022-11-17T18:00:57.379551614Z"`
Height uint32 `json:"height" example:"5467"`
Syncing bool `json:"syncing" example:"true"`
Timestamp int64 `json:"blockTimestamp" swaggertype:"string" format:"date-time" example:"2022-11-17T18:00:57.379551614Z"`
TransactionCount uint64 `json:"transactionCount" example:"554"`
ValidatorCount uint32 `json:"validatorCount" example:"5"`
VoteCount uint64 `json:"voteCount" example:"432"`
CircuitVersion string `json:"circuitVersion" example:"v1.0.0"`
MaxCensusSize uint64 `json:"maxCensusSize" example:"50000"`
NetworkCapacity uint64 `json:"networkCapacity" example:"2000"`
}

type Account struct {
Expand Down
6 changes: 3 additions & 3 deletions api/censuses.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,9 @@ func (a *API) censusCreateHandler(msg *apirest.APIdata, ctx *httprouter.HTTPCont
}

// get census max levels from vochain app if available
maxLevels := circuit.CircuitsConfigurations[circuit.DefaultCircuitConfigurationTag].Levels
if a.vocapp != nil {
maxLevels = a.vocapp.TransactionHandler.ZkCircuit.Config.Levels
maxLevels := circuit.CircuitsConfigurations[circuit.DefaultZkCircuitVersion].Levels
if a.vocapp != nil && a.vocapp.TransactionHandler != nil {
maxLevels = a.vocapp.TransactionHandler.ZkCircuit().Config.Levels
}

censusID := util.RandomBytes(32)
Expand Down
35 changes: 16 additions & 19 deletions api/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"time"

tmtypes "github.com/cometbft/cometbft/types"
"go.vocdoni.io/dvote/crypto/zk/circuit"
"go.vocdoni.io/dvote/httprouter"
"go.vocdoni.io/dvote/httprouter/apirest"
"go.vocdoni.io/dvote/types"
Expand Down Expand Up @@ -301,20 +300,20 @@ func (a *API) chainInfoHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext)
}

data, err := json.Marshal(&ChainInfo{
ID: a.vocapp.ChainID(),
BlockTime: *a.vocinfo.BlockTimes(),
ElectionCount: a.indexer.CountTotalProcesses(),
OrganizationCount: a.indexer.CountTotalEntities(),
Height: a.vocapp.Height(),
Syncing: a.vocapp.IsSynchronizing(),
TransactionCount: transactionCount,
ValidatorCount: uint32(len(validators)),
Timestamp: a.vocapp.Timestamp(),
VoteCount: voteCount,
GenesisTime: a.vocapp.Genesis().GenesisTime,
CircuitConfigurationTag: a.vocapp.CircuitConfigurationTag(),
MaxCensusSize: maxCensusSize,
NetworkCapacity: networkCapacity,
ID: a.vocapp.ChainID(),
BlockTime: *a.vocinfo.BlockTimes(),
ElectionCount: a.indexer.CountTotalProcesses(),
OrganizationCount: a.indexer.CountTotalEntities(),
Height: a.vocapp.Height(),
Syncing: a.vocapp.IsSynchronizing(),
TransactionCount: transactionCount,
ValidatorCount: uint32(len(validators)),
Timestamp: a.vocapp.Timestamp(),
VoteCount: voteCount,
GenesisTime: a.vocapp.Genesis().GenesisTime,
CircuitVersion: a.vocapp.TransactionHandler.ZkCircuitVersion(),
MaxCensusSize: maxCensusSize,
NetworkCapacity: networkCapacity,
})
if err != nil {
return err
Expand All @@ -332,10 +331,8 @@ func (a *API) chainInfoHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext)
// @Success 200 {object} circuit.ZkCircuitConfig
// @Router /chain/info/circuit [get]
func (a *API) chainCircuitInfoHandler(_ *apirest.APIdata, ctx *httprouter.HTTPContext) error {
// Get current circuit tag
circuitConfig := circuit.GetCircuitConfiguration(a.vocapp.CircuitConfigurationTag())
// Encode the circuit configuration to JSON
data, err := json.Marshal(circuitConfig)
// Encode the current circuit configuration to JSON
data, err := json.Marshal(a.vocapp.TransactionHandler.ZkCircuit().Config)
if err != nil {
return err
}
Expand Down
12 changes: 4 additions & 8 deletions apiclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,17 @@ func NewHTTPclient(addr *url.URL, bearerToken *uuid.UUID) (*HTTPclient, error) {
addr: addr,
retries: DefaultRetries,
}
data, status, err := c.Request(HTTPGET, nil, "chain", "info")
data, status, err := c.Request(HTTPGET, nil, "chain", "info", "circuit")
if err != nil {
return nil, err
}
if status != apirest.HTTPstatusOK {
log.Warnw("cannot get chain info from API server", "status", status, "data", data)
log.Warnw("cannot get circuit info from API server", "status", status, "data", data)
return c, nil
}
info := &api.ChainInfo{}
if err := json.Unmarshal(data, info); err != nil {
return nil, fmt.Errorf("cannot get chain ID from API server")
if err := json.Unmarshal(data, c.circuit); err != nil {
return nil, fmt.Errorf("cannot get unmarshsal circuit from API server")
}
c.chainID = info.ID
// Get the default circuit config
c.circuit = circuit.GetCircuitConfiguration(info.CircuitConfigurationTag)
return c, nil
}

Expand Down
27 changes: 27 additions & 0 deletions config/forks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package config

// ForksCfg allows applying softforks at specified heights
type ForksCfg struct {
VoceremonyForkBlock uint32
}

// Forks is a map of chainIDs
var Forks = map[string]*ForksCfg{
"vocdoni/DEV/29": {
VoceremonyForkBlock: 180000, // estimated 2023-11-30T23:45:29.095375169Z
},
"vocdoni/STAGE/9": {
VoceremonyForkBlock: 190000, // estimated 2023-12-04T11:25:38.643517797Z
},
"vocdoni/LTS/1.2": {
VoceremonyForkBlock: 350000, // estimated 2023-12-06T05:59:27.529386884Z
},
}

// ForksForChainID returns the ForksCfg of chainID, if found, or an empty ForksCfg otherwise
func ForksForChainID(chainID string) *ForksCfg {
if cfg, found := Forks[chainID]; found {
return cfg
}
return &ForksCfg{}
}
33 changes: 27 additions & 6 deletions crypto/zk/circuit/circuit.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import (
"path/filepath"
"time"

"go.vocdoni.io/dvote/config"
"go.vocdoni.io/dvote/log"
)

var downloadCircuitsTimeout = time.Minute * 5
const downloadCircuitsTimeout = time.Minute * 5

// BaseDir is where the artifact cache is expected to be found.
// If the artifacts are not found there, they will be downloaded and stored.
Expand All @@ -40,11 +41,31 @@ type ZkCircuit struct {
Config *ZkCircuitConfig
}

// LoadZkCircuitByTag gets the circuit configuration associated to the provided
// tag or gets the default one and load its artifacts to prepare the circuit to
// be used.
func LoadZkCircuitByTag(configTag string) (*ZkCircuit, error) {
circuitConf := GetCircuitConfiguration(configTag)
// DownloadDefaultZkCircuit ensures the default circuit is cached locally
func DownloadDefaultZkCircuit() error {
_, err := LoadZkCircuitVersion(DefaultZkCircuitVersion)
if err != nil {
return fmt.Errorf("could not load zk verification keys: %w", err)
}
return nil
}

// DownloadZkCircuitsForChainID ensures all circuits needed for chainID are cached locally
func DownloadZkCircuitsForChainID(chainID string) error {
if config.ForksForChainID(chainID).VoceremonyForkBlock > 0 {
_, err := LoadZkCircuitVersion(PreVoceremonyForkZkCircuitVersion)
if err != nil {
return fmt.Errorf("could not load zk verification keys: %w", err)
}
}
return DownloadDefaultZkCircuit()
}

// LoadZkCircuitVersion load the circuit artifacts based on the version provided.
// First, tries to load the artifacts from local storage, if they are not
// available, tries to download from their remote location.
func LoadZkCircuitVersion(version string) (*ZkCircuit, error) {
circuitConf := GetCircuitConfiguration(version)
ctx, cancel := context.WithTimeout(context.Background(), downloadCircuitsTimeout)
defer cancel()
return LoadZkCircuit(ctx, circuitConf)
Expand Down
43 changes: 23 additions & 20 deletions crypto/zk/circuit/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,10 @@ import (
"go.vocdoni.io/dvote/util"
)

// DefaultCircuitConfigurationTag constant contains the tag value that points
// to the default ZkSnark circuit configuration. It ensures that at least one
// circuit configuration is available so the configuration referred by this tag
// must be defined.
const DefaultCircuitConfigurationTag = "dev"

// ZkCircuitConfig defines the configuration of the files to be downloaded
type ZkCircuitConfig struct {
// Version of the published circuit
Version string
// URI defines the URI from where to download the files
URI string `json:"uri"`
// CircuitPath defines the path from where the files are downloaded.
Expand Down Expand Up @@ -75,12 +71,19 @@ func (conf *ZkCircuitConfig) SupportsCensusSize(maxCensusSize uint64) bool {
return conf.MaxCensusSize().Cmp(new(big.Int).SetUint64(maxCensusSize)) > 0
}

// DefaultZkCircuitVersion is the circuit version used by default
const DefaultZkCircuitVersion = "v1.0.0"

// PreVoceremonyForkZkCircuitVersion is the circuit version used before VoceremonyForkBlock
const PreVoceremonyForkZkCircuitVersion = "v0.0.1"

// CircuitsConfigurations stores the relation between the different vochain nets
// and the associated circuit configuration. Any circuit configuration must have
// the remote and local location of the circuits artifacts and their metadata
// such as artifacts hash or the number of parameters.
var CircuitsConfigurations = map[string]*ZkCircuitConfig{
"dev": {
"v0.0.1": {
Version: "v0.0.1",
URI: "https://raw.githubusercontent.com/vocdoni/" +
"zk-franchise-proof-circuit/master",
CircuitPath: "artifacts/zkCensus/dev/160",
Expand All @@ -92,29 +95,29 @@ var CircuitsConfigurations = map[string]*ZkCircuitConfig{
WasmHash: hexToBytes("0x80a73567f6a4655d4332301efcff4bc5711bb48176d1c71fdb1e48df222ac139"),
WasmFilename: "circuit.wasm",
},
"prod": {
URI: "https://raw.githubusercontent.com/vocdoni/" +
"zk-franchise-proof-circuit/master",
CircuitPath: "artifacts/zkCensus/dev/160",
"v1.0.0": {
Version: "v1.0.0",
URI: "https://raw.githubusercontent.com/altergui/zk-voceremony",
CircuitPath: "ceremony/vocdoni-zkcensus-ceremony/results",
Levels: 160, // ZkCircuit number of levels
ProvingKeyHash: hexToBytes("0xe359b256e5e3c78acaccf8dab5dc4bea99a2f07b2a05e935b5ca658c714dea4a"),
ProvingKeyFilename: "proving_key.zkey",
VerificationKeyHash: hexToBytes("0x235e55571812f8e324e73e37e53829db0c4ac8f68469b9b953876127c97b425f"),
VerificationKeyFilename: "verification_key.json",
WasmHash: hexToBytes("0x80a73567f6a4655d4332301efcff4bc5711bb48176d1c71fdb1e48df222ac139"),
WasmFilename: "circuit.wasm",
ProvingKeyHash: hexToBytes("0x94f4062db3e43175ac1136f285551d547a177e37b0616a41900a38ed5ec3d478"),
ProvingKeyFilename: "census_proving_key.zkey",
VerificationKeyHash: hexToBytes("0x2a47ff7e511926290fedfa406886944eeb0a3df9021ca26333c0c124c89aa7b0"),
VerificationKeyFilename: "census_verification_key.json",
WasmHash: hexToBytes("0xc98133cf4d84ced677549e0d848739f4e80ddf78af678cbc8b95377247a92773"),
WasmFilename: "census.wasm",
},
}

// GetCircuitConfiguration returns the circuit configuration associated with the
// provided tag or gets the default one.
func GetCircuitConfiguration(configTag string) *ZkCircuitConfig {
func GetCircuitConfiguration(version string) *ZkCircuitConfig {
// check if the provided config tag exists and return it if it does
if conf, ok := CircuitsConfigurations[configTag]; ok {
if conf, ok := CircuitsConfigurations[version]; ok {
return conf
}
// if not, return default configuration
return CircuitsConfigurations[DefaultCircuitConfigurationTag]
return CircuitsConfigurations[DefaultZkCircuitVersion]
}

// hexToBytes parses a hex string and returns the byte array from it. Warning,
Expand Down
33 changes: 14 additions & 19 deletions vochain/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ type BaseApplication struct {
// abcitypes.RequestBeginBlock.Header.Time
startBlockTimestamp atomic.Int64
chainID string
circuitConfigTag string
dataDir string
genesisInfo *tmtypes.GenesisDoc

Expand Down Expand Up @@ -136,10 +135,11 @@ func NewBaseApplication(vochainCfg *config.VochainCfg) (*BaseApplication, error)
istc,
filepath.Join(vochainCfg.DataDir, "txHandler"),
)
// Load or download the zk verification keys
if err := transactionHandler.LoadZkCircuit(circuit.DefaultCircuitConfigurationTag); err != nil {

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

blockCache, err := lru.New[int64, *tmtypes.Block](32)
if err != nil {
return nil, err
Expand All @@ -150,7 +150,6 @@ func NewBaseApplication(vochainCfg *config.VochainCfg) (*BaseApplication, error)
TransactionHandler: transactionHandler,
blockCache: blockCache,
dataDir: vochainCfg.DataDir,
circuitConfigTag: circuit.DefaultCircuitConfigurationTag,
genesisInfo: &tmtypes.GenesisDoc{},
}, nil
}
Expand Down Expand Up @@ -260,6 +259,10 @@ func (app *BaseApplication) beginBlock(t time.Time, height uint32) {
app.State.Rollback()
app.startBlockTimestamp.Store(t.Unix())
app.State.SetHeight(height)
err := app.SetZkCircuit()
if err != nil {
log.Fatalf("failed to set ZkCircuit: %w", err)
}
go app.State.CachePurge(height)
app.State.OnBeginBlock(vstate.BeginBlock{
Height: int64(height),
Expand Down Expand Up @@ -352,22 +355,14 @@ func (app *BaseApplication) Genesis() *tmtypes.GenesisDoc {
return app.genesisInfo
}

// SetCircuitConfigTag sets the current BaseApplication circuit config tag
// attribute to the provided one and loads the circuit configuration based on
// it. The available circuit config tags are defined in
// /crypto/zk/circuit/config.go
func (app *BaseApplication) SetCircuitConfigTag(tag string) error {
// Update the loaded circuit of the current app transactionHandler
if err := app.TransactionHandler.LoadZkCircuit(tag); err != nil {
return fmt.Errorf("cannot load zk circuit: %w", err)
// SetZkCircuit ensures the TransactionHandler ZkCircuit is the correct for a chain that implements forks
func (app *BaseApplication) SetZkCircuit() error {
switch {
case app.Height() < config.ForksForChainID(app.chainID).VoceremonyForkBlock:
return app.TransactionHandler.SetZkCircuitVersion(circuit.PreVoceremonyForkZkCircuitVersion)
default: // for example, if VoceremonyForkBlock == 0, or if Height is past the fork
return app.TransactionHandler.SetZkCircuitVersion(circuit.DefaultZkCircuitVersion)
}
app.circuitConfigTag = tag
return nil
}

// CircuitConfigurationTag returns the Node CircuitConfigurationTag
func (app *BaseApplication) CircuitConfigurationTag() string {
return app.circuitConfigTag
}

// IsSynchronizing informs if the blockchain is synchronizing or not.
Expand Down
5 changes: 2 additions & 3 deletions vochain/hysteresis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ func TestHysteresis(t *testing.T) {

// create test app and load zk circuit
app := TestBaseApplication(t)
devCircuit, err := circuit.LoadZkCircuitByTag(circuit.DefaultCircuitConfigurationTag)
err := app.TransactionHandler.SetZkCircuitVersion(circuit.DefaultZkCircuitVersion)
c.Assert(err, qt.IsNil)
app.TransactionHandler.ZkCircuit = devCircuit

// initial accounts
testWeight := big.NewInt(10)
Expand Down Expand Up @@ -94,7 +93,7 @@ func TestHysteresis(t *testing.T) {
encInputs, err := json.Marshal(inputs)
c.Assert(err, qt.IsNil)

zkProof, err := prover.Prove(devCircuit.ProvingKey, devCircuit.Wasm, encInputs)
zkProof, err := prover.Prove(app.TransactionHandler.ZkCircuit().ProvingKey, app.TransactionHandler.ZkCircuit().Wasm, encInputs)
c.Assert(err, qt.IsNil)

protoZkProof, err := zk.ProverProofToProtobufZKProof(zkProof, nil, nil, nil, nil, nil)
Expand Down
4 changes: 4 additions & 0 deletions vochain/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"go.vocdoni.io/dvote/config"
"go.vocdoni.io/dvote/crypto/ethereum"
"go.vocdoni.io/dvote/crypto/zk/circuit"
vocdoniGenesis "go.vocdoni.io/dvote/vochain/genesis"

tmcfg "github.com/cometbft/cometbft/config"
Expand Down Expand Up @@ -275,6 +276,9 @@ func newTendermint(app *BaseApplication,
log.Infow("genesis file", "genesis", tconfig.GenesisFile(), "chainID", genesisCID.ChainID)
app.SetChainID(genesisCID.ChainID)

// the chain might need additional ZkCircuits, now that we know the chainID ensure they are downloaded
circuit.DownloadZkCircuitsForChainID(genesisCID.ChainID)

// assign the default tendermint methods
app.SetDefaultMethods()
node, err := tmnode.NewNode(tconfig,
Expand Down
Loading

0 comments on commit f7dd973

Please sign in to comment.