Skip to content

Commit

Permalink
vochain: allow set maxCensusSize during election
Browse files Browse the repository at this point in the history
Now the SET_PROCESS_CENSUS transaction allows a new field
named "censusSize" which can be used to extend the current
maxCensusSize of the elelction.

The cost for this transaction is the difference between
the original cost and the new cost, plus the cost of SetProcessTx

Signed-off-by: p4u <[email protected]>
  • Loading branch information
p4u committed Jun 11, 2024
1 parent a4e9e47 commit dc27947
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 97 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,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.6-0.20240209115732-27836380ccae
go.vocdoni.io/proto v1.15.8
golang.org/x/crypto v0.22.0
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8
golang.org/x/net v0.24.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1597,8 +1597,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.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.vocdoni.io/proto v1.15.6-0.20240209115732-27836380ccae h1:7cqLdWmNi+iPqmKnvPnb4L92yPLvffMP07q0P26HMSM=
go.vocdoni.io/proto v1.15.6-0.20240209115732-27836380ccae/go.mod h1:oi/WtiBFJ6QwNDv2aUQYwOnUKzYuS/fBqXF8xDNwcGo=
go.vocdoni.io/proto v1.15.8 h1:I5HVHffQwXyp0jootnCVj83+PoJ8L703RJ2CFSPTa2Q=
go.vocdoni.io/proto v1.15.8/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
2 changes: 1 addition & 1 deletion vochain/indexer/indexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1427,7 +1427,7 @@ func TestCensusUpdate(t *testing.T) {
newCensusRoot := util.RandomBytes(32)
newCensusURI := new(string)
*newCensusURI = "ipfs://5678"
if err := app.State.SetProcessCensus(pid, newCensusRoot, *newCensusURI, true); err != nil {
if err := app.State.SetProcessCensus(pid, newCensusRoot, *newCensusURI, 0, true); err != nil {
t.Fatal(err)
}

Expand Down
152 changes: 101 additions & 51 deletions vochain/process_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestNewProcessCheckTxDeliverTxCommitTransitions(t *testing.T) {
}

// create process with entityID (should work)
qt.Assert(t, testNewProcess(t, process.ProcessId, accounts[0], app, process), qt.IsNil)
qt.Assert(t, testCreateProcess(t, accounts[0], app, process), qt.IsNotNil)
// all get accounts assume account is not nil
entityAcc, err := app.State.GetAccount(accounts[0].Address(), false)
qt.Assert(t, err, qt.IsNil)
Expand All @@ -49,7 +49,7 @@ func TestNewProcessCheckTxDeliverTxCommitTransitions(t *testing.T) {
qt.Assert(t, entityAcc.ProcessIndex, qt.Equals, uint32(1))

// create process with delegate (should work)
qt.Assert(t, testNewProcess(t, process.ProcessId, accounts[1], app, process), qt.IsNil)
qt.Assert(t, testCreateProcess(t, accounts[1], app, process), qt.IsNotNil)
entityAcc, err = app.State.GetAccount(accounts[0].Address(), false)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, entityAcc.Balance, qt.Equals, uint64(9990))
Expand All @@ -62,7 +62,7 @@ func TestNewProcessCheckTxDeliverTxCommitTransitions(t *testing.T) {
qt.Assert(t, delegateAcc.ProcessIndex, qt.Equals, uint32(0))

// create process with a non delegate to another entityID (should not work)
qt.Assert(t, testNewProcess(t, process.ProcessId, accounts[2], app, process), qt.IsNotNil)
qt.Assert(t, testCreateProcess(t, accounts[2], app, process), qt.IsNil)
entityAcc, err = app.State.GetAccount(accounts[0].Address(), false)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, entityAcc.Balance, qt.Equals, uint64(9990))
Expand All @@ -76,50 +76,22 @@ func TestNewProcessCheckTxDeliverTxCommitTransitions(t *testing.T) {

// create process with status PAUSED (should work)
process.Status = models.ProcessStatus_PAUSED
qt.Assert(t, testNewProcess(t, process.ProcessId, accounts[1], app, process), qt.IsNil)
qt.Assert(t, testCreateProcess(t, accounts[1], app, process), qt.IsNotNil)
// create process with status different than READY or PAUSED (should not work)
process.Status = models.ProcessStatus_CANCELED
qt.Assert(t, testNewProcess(t, process.ProcessId, accounts[1], app, process),
qt.Assert(t, testCreateProcessWithErr(t, accounts[1], app, process),
qt.ErrorMatches, ".*status must be READY or PAUSED.*")
process.Status = models.ProcessStatus_PROCESS_UNKNOWN
qt.Assert(t, testNewProcess(t, process.ProcessId, accounts[1], app, process),
qt.Assert(t, testCreateProcessWithErr(t, accounts[1], app, process),
qt.ErrorMatches, ".*status must be READY or PAUSED.*")
process.Status = models.ProcessStatus_ENDED
qt.Assert(t, testNewProcess(t, process.ProcessId, accounts[1], app, process),
qt.Assert(t, testCreateProcessWithErr(t, accounts[1], app, process),
qt.ErrorMatches, ".*status must be READY or PAUSED.*")
process.Status = models.ProcessStatus_RESULTS
qt.Assert(t, testNewProcess(t, process.ProcessId, accounts[1], app, process),
qt.Assert(t, testCreateProcessWithErr(t, accounts[1], app, process),
qt.ErrorMatches, ".*status must be READY or PAUSED.*")
}

func testNewProcess(t *testing.T, _ []byte, txSender *ethereum.SignKeys,
app *BaseApplication, process *models.Process) error {
var stx models.SignedTx
var err error

// assumes account is not nil
txSenderAcc, err := app.State.GetAccount(txSender.Address(), false)
if err != nil {
return fmt.Errorf("cannot get tx sender account %s with error %w", txSender.Address(), err)
}
// create tx
tx := &models.NewProcessTx{
Txtype: models.TxType_NEW_PROCESS,
Nonce: txSenderAcc.Nonce,
Process: process,
}
stx.Tx, err = proto.Marshal(&models.Tx{Payload: &models.Tx_NewProcess{NewProcess: tx}})
if err != nil {
return fmt.Errorf("cannot mashal tx %w", err)
}
if stx.Signature, err = txSender.SignVocdoniTx(stx.Tx, app.chainID); err != nil {
return fmt.Errorf("cannot sign tx %+v with error %w", tx, err)
}

_, err = testCheckTxDeliverTxCommit(t, app, &stx)
return err
}

func TestProcessSetStatusCheckTxDeliverTxCommitTransitions(t *testing.T) {
app, keys := createTestBaseApplicationAndAccounts(t, 10)

Expand Down Expand Up @@ -338,20 +310,20 @@ func TestProcessSetCensusCheckTxDeliverTxCommitTransitions(t *testing.T) {
qt.Assert(t, app.State.AddProcess(process3), qt.IsNil)

// Set census (should work)
qt.Assert(t, testSetProcessCensus(t, pid, keys[0], app, []byte{1, 2, 3}, &censusURI2), qt.IsNil)
qt.Assert(t, testSetProcessCensus(t, pid, keys[0], app, []byte{1, 2, 3}, &censusURI2, 0), qt.IsNil)

// Set census by delegate (should work)
qt.Assert(t, testSetProcessCensus(t, pid, keys[1], app, []byte{3, 2, 1}, &censusURI2), qt.IsNil)
qt.Assert(t, testSetProcessCensus(t, pid, keys[1], app, []byte{3, 2, 1}, &censusURI2, 0), qt.IsNil)

// Set census (should not work)
qt.Assert(t, testSetProcessCensus(t, pid2, keys[0], app, []byte{1, 2, 3}, &censusURI2), qt.IsNotNil)
qt.Assert(t, testSetProcessCensus(t, pid2, keys[0], app, []byte{1, 2, 3}, &censusURI2, 0), qt.IsNotNil)

// Set census (should not work)
qt.Assert(t, testSetProcessCensus(t, pid3, keys[2], app, []byte{1, 2, 3}, &censusURI2), qt.IsNotNil)
qt.Assert(t, testSetProcessCensus(t, pid3, keys[2], app, []byte{1, 2, 3}, &censusURI2, 0), qt.IsNotNil)
}

func testSetProcessCensus(t *testing.T, pid []byte, txSender *ethereum.SignKeys,
app *BaseApplication, censusRoot []byte, censusURI *string) error {
app *BaseApplication, censusRoot []byte, censusURI *string, censusSize uint64) error {
var stx models.SignedTx
var err error

Expand All @@ -366,10 +338,9 @@ func testSetProcessCensus(t *testing.T, pid []byte, txSender *ethereum.SignKeys,
ProcessId: pid,
CensusRoot: censusRoot,
CensusURI: censusURI,
CensusSize: &censusSize,
}
if stx.Tx, err = proto.Marshal(&models.Tx{
Payload: &models.Tx_SetProcess{SetProcess: tx}},
); err != nil {
if stx.Tx, err = proto.Marshal(&models.Tx{Payload: &models.Tx_SetProcess{SetProcess: tx}}); err != nil {
return fmt.Errorf("cannot mashal tx %w", err)
}
if stx.Signature, err = txSender.SignVocdoniTx(stx.Tx, app.chainID); err != nil {
Expand Down Expand Up @@ -437,7 +408,21 @@ func createTestBaseApplicationAndAccounts(t *testing.T,
}

// testCreateProcess creates a process with the given parameters via transaction.
// It returns the process ID if the transaction was successful, or nil otherwise.
func testCreateProcess(t *testing.T, txSender *ethereum.SignKeys, app *BaseApplication, process *models.Process) []byte {
pid, err := testCreateProcessWithErrAndData(t, txSender, app, process)
if err != nil {
return nil
}
return pid
}

func testCreateProcessWithErr(t *testing.T, txSender *ethereum.SignKeys, app *BaseApplication, process *models.Process) error {
_, err := testCreateProcessWithErrAndData(t, txSender, app, process)
return err
}

func testCreateProcessWithErrAndData(t *testing.T, txSender *ethereum.SignKeys, app *BaseApplication, process *models.Process) ([]byte, error) {
var stx models.SignedTx

// assume account is not nil
Expand All @@ -456,9 +441,7 @@ func testCreateProcess(t *testing.T, txSender *ethereum.SignKeys, app *BaseAppli
stx.Signature, err = txSender.SignVocdoniTx(stx.Tx, app.chainID)
qt.Assert(t, err, qt.IsNil, qt.Commentf("cannot sign tx %+v with error %w", tx, err))

data, err := testCheckTxDeliverTxCommit(t, app, &stx)
qt.Assert(t, err, qt.IsNil)
return data
return testCheckTxDeliverTxCommit(t, app, &stx)
}

func testCheckTxDeliverTxCommit(t *testing.T, app *BaseApplication, stx *models.SignedTx) ([]byte, error) {
Expand Down Expand Up @@ -494,9 +477,7 @@ func TestGlobalMaxProcessSize(t *testing.T) {

// define process
censusURI := ipfsUrlTest
pid := util.RandomBytes(types.ProcessIDsize)
process := &models.Process{
ProcessId: pid,
StartBlock: 1,
EnvelopeType: &models.EnvelopeType{EncryptedVotes: false},
Mode: &models.ProcessMode{Interruptible: true},
Expand All @@ -511,9 +492,78 @@ func TestGlobalMaxProcessSize(t *testing.T) {
}

// create process with entityID (should fail)
qt.Assert(t, testNewProcess(t, process.ProcessId, accounts[0], app, process), qt.IsNotNil)
qt.Assert(t, testCreateProcess(t, accounts[0], app, process), qt.IsNil)

// create process with entityID (should work)
process.MaxCensusSize = 5
qt.Assert(t, testNewProcess(t, process.ProcessId, accounts[0], app, process), qt.IsNil)
qt.Assert(t, testCreateProcess(t, accounts[0], app, process), qt.IsNotNil)
}

func TestSetProcessCensusSize(t *testing.T) {
app, accounts := createTestBaseApplicationAndAccounts(t, 10)
app.State.ElectionPriceCalc.SetBasePrice(10)
app.State.ElectionPriceCalc.SetCapacity(2000)

// define process
censusURI := ipfsUrlTest
process := &models.Process{
StartBlock: 1,
EnvelopeType: &models.EnvelopeType{EncryptedVotes: false},
Mode: &models.ProcessMode{Interruptible: true, DynamicCensus: true},
VoteOptions: &models.ProcessVoteOptions{MaxCount: 16, MaxValue: 16},
Status: models.ProcessStatus_READY,
EntityId: accounts[0].Address().Bytes(),
CensusRoot: util.RandomBytes(32),
CensusURI: &censusURI,
CensusOrigin: models.CensusOrigin_OFF_CHAIN_TREE,
Duration: 60 * 60,
MaxCensusSize: 2,
}

// create the process
pid := testCreateProcess(t, accounts[0], app, process)
app.AdvanceTestBlock()

proc, err := app.State.Process(pid, true)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, proc.MaxCensusSize, qt.Equals, uint64(2))

// Set census size and new root (should work)
qt.Assert(t, testSetProcessCensus(t, pid, accounts[0], app, util.RandomBytes(32), &censusURI, 5), qt.IsNil)
app.AdvanceTestBlock()

proc, err = app.State.Process(pid, true)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, proc.MaxCensusSize, qt.Equals, uint64(5))

// Set census size (without new root) (should work)
qt.Assert(t, testSetProcessCensus(t, pid, accounts[0], app, nil, nil, 10), qt.IsNil)
app.AdvanceTestBlock()

proc, err = app.State.Process(pid, true)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, proc.MaxCensusSize, qt.Equals, uint64(10))
qt.Assert(t, proc.CensusRoot, qt.IsNotNil)

// Set smaller census size (should fail)
qt.Assert(t, testSetProcessCensus(t, pid, accounts[0], app, nil, nil, 5), qt.IsNotNil)
app.AdvanceTestBlock()

proc, err = app.State.Process(pid, true)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, proc.MaxCensusSize, qt.Equals, uint64(10))

// Check cost is increased with larger census size (should work)
account, err := app.State.GetAccount(accounts[0].Address(), true)
qt.Assert(t, err, qt.IsNil)
oldBalance := account.Balance

qt.Assert(t, testSetProcessCensus(t, pid, accounts[0], app, nil, nil, 20000), qt.IsNil)

account, err = app.State.GetAccount(accounts[0].Address(), true)
qt.Assert(t, err, qt.IsNil)
newBalance := account.Balance

// check that newBalance is at least 100 tokens less than oldBalance
qt.Assert(t, oldBalance-newBalance >= 100, qt.IsTrue)
}
2 changes: 1 addition & 1 deletion vochain/state/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ func (v *State) BurnTxCostIncrementNonce(accountAddress common.Address, txType m
if err := acc.Transfer(burnAcc, cost); err != nil {
return fmt.Errorf("burnTxCostIncrementNonce: %w", err)
}
log.Debugf("burning fee for tx %s with cost %d from account %s", txType, cost, accountAddress)
log.Debugw("burning fee", "txType", txType.String(), "cost", cost, "account", accountAddress.String())
if err := v.SetAccount(BurnAddress, burnAcc); err != nil {
return fmt.Errorf("burnTxCostIncrementNonce: %w", err)
}
Expand Down
29 changes: 20 additions & 9 deletions vochain/state/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ func (v *State) GetProcessResults(pid []byte) ([][]*types.BigInt, error) {
}

// SetProcessCensus sets the census for a given process, only if that process enables dynamic census
func (v *State) SetProcessCensus(pid, censusRoot []byte, censusURI string, commit bool) error {
func (v *State) SetProcessCensus(pid, censusRoot []byte, censusURI string, censusSize uint64, commit bool) error {
process, err := v.Process(pid, false)
if err != nil {
return err
Expand All @@ -336,18 +336,29 @@ func (v *State) SetProcessCensus(pid, censusRoot []byte, censusURI string, commi
"cannot update census, process status must be READY or PAUSED and is: %s",
process.Status.String())
}
// check not same censusRoot
if bytes.Equal(censusRoot, process.CensusRoot) {
return fmt.Errorf("cannot update census, same censusRoot")
}

if CensusOrigins[process.CensusOrigin].NeedsURI && censusURI == "" {
return fmt.Errorf("process requires URI but an empty one was provided")
// if maxCensusSize is 0, then we're trying to update census rather than updating the maxCensusSize,
// so censusRoot must be different and the censusURI must be provided
if censusSize == 0 {
if bytes.Equal(censusRoot, process.CensusRoot) {
return fmt.Errorf("cannot update census, same censusRoot")
}
if CensusOrigins[process.CensusOrigin].NeedsURI && censusURI == "" {
return fmt.Errorf("process requires URI but an empty one was provided")
}
}

// commit the change
if commit {
process.CensusRoot = censusRoot
process.CensusURI = &censusURI
if censusRoot != nil {
process.CensusRoot = censusRoot
}
if censusURI != "" {
process.CensusURI = &censusURI
}
if censusSize > 0 {
process.MaxCensusSize = uint64(censusSize)
}
if err := v.UpdateProcess(process, process.ProcessId); err != nil {
return err
}
Expand Down
Loading

0 comments on commit dc27947

Please sign in to comment.