diff --git a/contracts/validatorcontract/README.md b/contracts/validatorcontract/README.md new file mode 100644 index 000000000..fca08dc68 --- /dev/null +++ b/contracts/validatorcontract/README.md @@ -0,0 +1,88 @@ +# Validator Set Contract + +This package contains the reference validator set contract used with QBFT contract-based validator selection. + +## Files + +- `contract/validator_set.sol`: Solidity source for the validator set contract. +- `contract/validator_set.go`: generated Go binding. Do not edit by hand. +- `validator.go`: small Go wrapper around the generated binding. +- `validator_test.go`: simulated backend tests for the contract behavior. + +## Update Flow + +1. Edit `contract/validator_set.sol`. +2. Keep the Solidity pragma on a real compiler version supported by the local toolchain. The current source uses Solidity `^0.8.30`. +3. Preserve the QBFT compatibility method: + + ```solidity + function getValidators() external view returns (address[] memory) + ``` + + QBFT contract mode calls this method and expects it to return the active validator list. In this implementation, validators in maintenance are excluded from the returned list. + +4. Compile the contract to temporary Foundry artifacts: + + ```sh + forge build \ + --use 0.8.30 \ + --evm-version paris \ + --contracts contracts/validatorcontract/contract \ + --out /private/tmp/validatorcontract-out \ + --cache-path /private/tmp/validatorcontract-cache + ``` + + The `paris` EVM target keeps the generated bytecode compatible with the Go simulated backend used by the tests. + +5. Extract ABI and bytecode: + + ```sh + jq '.abi' /private/tmp/validatorcontract-out/validator_set.sol/ValidatorSet.json \ + > /private/tmp/ValidatorSet.abi + + jq -r '.bytecode.object' /private/tmp/validatorcontract-out/validator_set.sol/ValidatorSet.json \ + > /private/tmp/ValidatorSet.bin + ``` + +6. Regenerate the Go binding: + + ```sh + go run ./cmd/abigen \ + --abi /private/tmp/ValidatorSet.abi \ + --bin /private/tmp/ValidatorSet.bin \ + --pkg contract \ + --type ValidatorSet \ + --out contracts/validatorcontract/contract/validator_set.go + ``` + +7. Format generated and handwritten Go files: + + ```sh + gofmt -w \ + contracts/validatorcontract/validator.go \ + contracts/validatorcontract/validator_test.go \ + contracts/validatorcontract/contract/validator_set.go + ``` + +8. Run tests: + + ```sh + go test ./contracts/validatorcontract + go test ./contracts/... + ``` + +## Contract Compatibility Rules + +- `getValidators()` must remain present with the same signature. +- `getValidators()` must return only active validators. +- Governance quorum is based on all registered validators, not only active validators. +- `faultTolerance()` is calculated as `(N - 1) / 3`, where `N` is the number of registered validators. +- `quorumSize()` is calculated as `2f + 1`. +- Add/remove proposal IDs include the current validator set version, so the same address can be removed, re-added, and removed again in later versions. +- A validator in maintenance still has governance voting power. +- At most `f` validators may be in maintenance at the same time. + +## Notes + +- The generated binding includes deployment bytecode, so regenerate it whenever the Solidity source changes, including NatSpec-only changes that affect metadata. +- Do not commit temporary Foundry `cache/` or `out/` directories. Prefer the `/private/tmp/...` paths shown above. diff --git a/contracts/validatorcontract/contract/validator_set.go b/contracts/validatorcontract/contract/validator_set.go new file mode 100644 index 000000000..c8b67686f --- /dev/null +++ b/contracts/validatorcontract/contract/validator_set.go @@ -0,0 +1,1429 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package contract + +import ( + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// ValidatorSetABI is the input ABI used to generate the binding from. +const ValidatorSetABI = "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"initialValidators\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"enterMaintenance\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"exitMaintenance\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"faultTolerance\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAllValidators\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getValidators\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"hasVoted\",\"inputs\":[{\"name\":\"operation\",\"type\":\"uint8\",\"internalType\":\"enumValidatorSet.Operation\"},{\"name\":\"candidate\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"voter\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isInMaintenance\",\"inputs\":[{\"name\":\"validator\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isValidator\",\"inputs\":[{\"name\":\"validator\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"maintenanceSize\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"proposalExecuted\",\"inputs\":[{\"name\":\"operation\",\"type\":\"uint8\",\"internalType\":\"enumValidatorSet.Operation\"},{\"name\":\"candidate\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"proposalId\",\"inputs\":[{\"name\":\"operation\",\"type\":\"uint8\",\"internalType\":\"enumValidatorSet.Operation\"},{\"name\":\"candidate\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"proposalIdAtVersion\",\"inputs\":[{\"name\":\"operation\",\"type\":\"uint8\",\"internalType\":\"enumValidatorSet.Operation\"},{\"name\":\"candidate\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"versionNumber\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"proposalVotes\",\"inputs\":[{\"name\":\"operation\",\"type\":\"uint8\",\"internalType\":\"enumValidatorSet.Operation\"},{\"name\":\"candidate\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"quorumSize\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"version\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"voteAddValidator\",\"inputs\":[{\"name\":\"candidate\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"voteRemoveValidator\",\"inputs\":[{\"name\":\"candidate\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"MaintenanceEntered\",\"inputs\":[{\"name\":\"validator\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"MaintenanceExited\",\"inputs\":[{\"name\":\"validator\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorAdded\",\"inputs\":[{\"name\":\"validator\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorRemoved\",\"inputs\":[{\"name\":\"validator\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorVote\",\"inputs\":[{\"name\":\"proposalId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"operation\",\"type\":\"uint8\",\"indexed\":true,\"internalType\":\"enumValidatorSet.Operation\"},{\"name\":\"candidate\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"voter\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"},{\"name\":\"votes\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"quorum\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false}]" + +var ValidatorSetParsedABI, _ = abi.JSON(strings.NewReader(ValidatorSetABI)) + +// ValidatorSetBin is the compiled bytecode used for deploying new contracts. +var ValidatorSetBin = "0x608060405234801561001057600080fd5b50604051612c28380380612c288339818101604052810190610032919061048c565b6000815111610076576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161006d90610532565b60405180910390fd5b60005b81518110156100b8576100ab82828151811061009857610097610552565b5b60200260200101516100bf60201b60201c565b8080600101915050610079565b505061067f565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361012e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610125906105cd565b60405180910390fd5b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16156101bb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101b29061065f565b60405180910390fd5b60018060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff021916908315150217905550600080549050600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506000819080600181540180825580915050600190039060005260206000200160009091909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b6000604051905090565b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610323826102da565b810181811067ffffffffffffffff82111715610342576103416102eb565b5b80604052505050565b60006103556102c1565b9050610361828261031a565b919050565b600067ffffffffffffffff821115610381576103806102eb565b5b602082029050602081019050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006103c282610397565b9050919050565b6103d2816103b7565b81146103dd57600080fd5b50565b6000815190506103ef816103c9565b92915050565b600061040861040384610366565b61034b565b9050808382526020820190506020840283018581111561042b5761042a610392565b5b835b81811015610454578061044088826103e0565b84526020840193505060208101905061042d565b5050509392505050565b600082601f830112610473576104726102d5565b5b81516104838482602086016103f5565b91505092915050565b6000602082840312156104a2576104a16102cb565b5b600082015167ffffffffffffffff8111156104c0576104bf6102d0565b5b6104cc8482850161045e565b91505092915050565b600082825260208201905092915050565b7f56616c696461746f725365743a20656d7074792076616c696461746f72730000600082015250565b600061051c601e836104d5565b9150610527826104e6565b602082019050919050565b6000602082019050818103600083015261054b8161050f565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f56616c696461746f725365743a207a65726f2076616c696461746f7200000000600082015250565b60006105b7601c836104d5565b91506105c282610581565b602082019050919050565b600060208201905081810360008301526105e6816105aa565b9050919050565b7f56616c696461746f725365743a206475706c69636174652076616c696461746f60008201527f7200000000000000000000000000000000000000000000000000000000000000602082015250565b60006106496021836104d5565b9150610654826105ed565b604082019050919050565b600060208201905081810360008301526106788161063c565b9050919050565b61259a8061068e6000396000f3fe608060405234801561001057600080fd5b506004361061010b5760003560e01c80639369d7de116100a2578063dfa45bdd11610071578063dfa45bdd14610282578063e6c166e3146102b2578063eac942a9146102e2578063f3513a3714610312578063facd743b146103305761010b565b80639369d7de1461020e5780639bf6a74a14610218578063a8b4b21014610234578063b7ab4db5146102645761010b565b806357456f22116100de57806357456f221461017457806358753136146101905780635e401b3e146101c057806375b76f1c146101f05761010b565b806304c4fec61461011057806322e283691461011a5780633068ef4d1461013857806354fd4d5014610156575b600080fd5b610118610360565b005b61012261052d565b60405161012f91906117ba565b60405180910390f35b610140610537565b60405161014d91906117ba565b60405180910390f35b61015e61055e565b60405161016b91906117ba565b60405180910390f35b61018e60048036038101906101899190611838565b610568565b005b6101aa60048036038101906101a591906118b6565b6106fe565b6040516101b79190611922565b60405180910390f35b6101da60048036038101906101d5919061193d565b610734565b6040516101e791906119ab565b60405180910390f35b6101f86107a9565b60405161020591906117ba565b60405180910390f35b6102166107ce565b005b610232600480360381019061022d9190611838565b6109e7565b005b61024e600480360381019061024991906119c6565b610b55565b60405161025b9190611922565b60405180910390f35b61026c610b69565b6040516102799190611ac4565b60405180910390f35b61029c60048036038101906102979190611838565b610ce8565b6040516102a991906119ab565b60405180910390f35b6102cc60048036038101906102c791906119c6565b610d3e565b6040516102d991906117ba565b60405180910390f35b6102fc60048036038101906102f791906119c6565b610d68565b60405161030991906119ab565b60405180910390f35b61031a610d9f565b6040516103279190611ac4565b60405180910390f35b61034a60048036038101906103459190611838565b610ea3565b60405161035791906119ab565b60405180910390f35b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff166103ec576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103e390611b69565b60405180910390fd5b600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16610478576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161046f90611bd5565b60405180910390fd5b6000600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff021916908315150217905550600460008154809291906104e390611c24565b91905055503373ffffffffffffffffffffffffffffffffffffffff167f0203ae0082a4b3f0b71e461ab5245258799bdb5d52d0f224a6aaed75deca688960405160405180910390a2565b6000600454905090565b600060016105436107a9565b600261054f9190611c4d565b6105599190611c8f565b905090565b6000600554905090565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff166105f4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105eb90611b69565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610663576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161065a90611d0f565b60405180910390fd5b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16156106f0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106e790611da1565b60405180910390fd5b6106fb600082610ef9565b50565b600083838360405160200161071593929190611ec8565b6040516020818303038152906040528051906020012090509392505050565b60006006600061074486866111fb565b815260200190815260200160002060020160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1690509392505050565b6000600360016000805490506107bf9190611f05565b6107c99190611f68565b905090565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1661085a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161085190611b69565b60405180910390fd5b600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16156108e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108de9061200b565b60405180910390fd5b6108ef6107a9565b60045410610932576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109299061209d565b60405180910390fd5b6001600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055506004600081548092919061099d906120bd565b91905055503373ffffffffffffffffffffffffffffffffffffffff167f44aa86acf916265e97ff989ab2c2289b9f442a5a4769121a3c82d3daf7ea972b60405160405180910390a2565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16610a73576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a6a90611b69565b60405180910390fd5b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16610aff576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610af690612177565b60405180910390fd5b600160008054905011610b47576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b3e90612209565b60405180910390fd5b610b52600182610ef9565b50565b6000610b6183836111fb565b905092915050565b60606000600454600080549050610b809190611f05565b905060008167ffffffffffffffff811115610b9e57610b9d612229565b5b604051908082528060200260200182016040528015610bcc5781602001602082028036833780820191505090505b5090506000805b600080549050811015610cde576000808281548110610bf557610bf4612258565b5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600360008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16610cd05780848481518110610c8757610c86612258565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250508280610ccc906120bd565b9350505b508080600101915050610bd3565b5081935050505090565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff169050919050565b600060066000610d4e85856111fb565b815260200190815260200160002060000154905092915050565b600060066000610d7885856111fb565b815260200190815260200160002060010160009054906101000a900460ff16905092915050565b60606000808054905067ffffffffffffffff811115610dc157610dc0612229565b5b604051908082528060200260200182016040528015610def5781602001602082028036833780820191505090505b50905060005b600080549050811015610e9b5760008181548110610e1657610e15612258565b5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16828281518110610e5457610e53612258565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250508080600101915050610df5565b508091505090565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff169050919050565b6000610f0583836111fb565b905060006006600083815260200190815260200160002090508060010160009054906101000a900460ff1615610f70576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f67906122d3565b60405180910390fd5b8060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1615610fff576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ff69061233f565b60405180910390fd5b60018160020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff02191690831515021790555080600001600081548092919061106e906120bd565b9190505550600061107d610537565b90508373ffffffffffffffffffffffffffffffffffffffff168560018111156110a9576110a8611dc1565b5b847f88de5c1b4216be3907eda6a00c98004ce4a2076ccd207f384d40e92d288fdab6338660000154866040516110e19392919061236e565b60405180910390a4808260000154106111f45760018260010160006101000a81548160ff0219169083151502179055506000600181111561112557611124611dc1565b5b85600181111561113857611137611dc1565b5b0361118e5761114684611212565b8373ffffffffffffffffffffffffffffffffffffffff167fe366c1c0452ed8eec96861e9e54141ebff23c9ec89fe27b996b45f5ec388498760405160405180910390a26111db565b61119784611414565b8373ffffffffffffffffffffffffffffffffffffffff167fe1434e25d6611e0db941968fdc97811c982ac1602e951637d206f5fdda9dd8f160405160405180910390a25b600560008154809291906111ee906120bd565b91905055505b5050505050565b600061120a83836005546106fe565b905092915050565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603611281576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611278906123f1565b60405180910390fd5b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff161561130e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161130590612483565b60405180910390fd5b60018060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff021916908315150217905550600080549050600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506000819080600181540180825580915050600190039060005260206000200160009091909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff166114a0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161149790612515565b60405180910390fd5b600360008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1615611563576000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055506004600081548092919061155d90611c24565b91905055505b6000600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905060008190505b60016000805490506115bf9190611f05565b8110156116c3576000806001836115d69190611c8f565b815481106115e7576115e6612258565b5b9060005260206000200160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050806000838154811061162957611628612258565b5b9060005260206000200160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555081600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505080806001019150506115ad565b5060008054806116d6576116d5612535565b5b6001900381819060005260206000200160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690559055600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009055600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81549060ff02191690555050565b6000819050919050565b6117b4816117a1565b82525050565b60006020820190506117cf60008301846117ab565b92915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000611805826117da565b9050919050565b611815816117fa565b811461182057600080fd5b50565b6000813590506118328161180c565b92915050565b60006020828403121561184e5761184d6117d5565b5b600061185c84828501611823565b91505092915050565b6002811061187257600080fd5b50565b60008135905061188481611865565b92915050565b611893816117a1565b811461189e57600080fd5b50565b6000813590506118b08161188a565b92915050565b6000806000606084860312156118cf576118ce6117d5565b5b60006118dd86828701611875565b93505060206118ee86828701611823565b92505060406118ff868287016118a1565b9150509250925092565b6000819050919050565b61191c81611909565b82525050565b60006020820190506119376000830184611913565b92915050565b600080600060608486031215611956576119556117d5565b5b600061196486828701611875565b935050602061197586828701611823565b925050604061198686828701611823565b9150509250925092565b60008115159050919050565b6119a581611990565b82525050565b60006020820190506119c0600083018461199c565b92915050565b600080604083850312156119dd576119dc6117d5565b5b60006119eb85828601611875565b92505060206119fc85828601611823565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b611a3b816117fa565b82525050565b6000611a4d8383611a32565b60208301905092915050565b6000602082019050919050565b6000611a7182611a06565b611a7b8185611a11565b9350611a8683611a22565b8060005b83811015611ab7578151611a9e8882611a41565b9750611aa983611a59565b925050600181019050611a8a565b5085935050505092915050565b60006020820190508181036000830152611ade8184611a66565b905092915050565b600082825260208201905092915050565b7f56616c696461746f725365743a2073656e646572206973206e6f742076616c6960008201527f6461746f72000000000000000000000000000000000000000000000000000000602082015250565b6000611b53602583611ae6565b9150611b5e82611af7565b604082019050919050565b60006020820190508181036000830152611b8281611b46565b9050919050565b7f56616c696461746f725365743a206e6f7420696e206d61696e74656e616e6365600082015250565b6000611bbf602083611ae6565b9150611bca82611b89565b602082019050919050565b60006020820190508181036000830152611bee81611bb2565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000611c2f826117a1565b915060008203611c4257611c41611bf5565b5b600182039050919050565b6000611c58826117a1565b9150611c63836117a1565b9250828202611c71816117a1565b91508282048414831517611c8857611c87611bf5565b5b5092915050565b6000611c9a826117a1565b9150611ca5836117a1565b9250828201905080821115611cbd57611cbc611bf5565b5b92915050565b7f56616c696461746f725365743a207a65726f2063616e64696461746500000000600082015250565b6000611cf9601c83611ae6565b9150611d0482611cc3565b602082019050919050565b60006020820190508181036000830152611d2881611cec565b9050919050565b7f56616c696461746f725365743a2063616e6469646174652069732076616c696460008201527f61746f7200000000000000000000000000000000000000000000000000000000602082015250565b6000611d8b602483611ae6565b9150611d9682611d2f565b604082019050919050565b60006020820190508181036000830152611dba81611d7e565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60028110611e0157611e00611dc1565b5b50565b6000819050611e1282611df0565b919050565b6000611e2282611e04565b9050919050565b60008160f81b9050919050565b6000611e4182611e29565b9050919050565b611e59611e5482611e17565b611e36565b82525050565b60008160601b9050919050565b6000611e7782611e5f565b9050919050565b6000611e8982611e6c565b9050919050565b611ea1611e9c826117fa565b611e7e565b82525050565b6000819050919050565b611ec2611ebd826117a1565b611ea7565b82525050565b6000611ed48286611e48565b600182019150611ee48285611e90565b601482019150611ef48284611eb1565b602082019150819050949350505050565b6000611f10826117a1565b9150611f1b836117a1565b9250828203905081811115611f3357611f32611bf5565b5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6000611f73826117a1565b9150611f7e836117a1565b925082611f8e57611f8d611f39565b5b828204905092915050565b7f56616c696461746f725365743a20616c726561647920696e206d61696e74656e60008201527f616e636500000000000000000000000000000000000000000000000000000000602082015250565b6000611ff5602483611ae6565b915061200082611f99565b604082019050919050565b6000602082019050818103600083015261202481611fe8565b9050919050565b7f56616c696461746f725365743a20746f6f206d616e792076616c696461746f7260008201527f7320696e206d61696e74656e616e636500000000000000000000000000000000602082015250565b6000612087603083611ae6565b91506120928261202b565b604082019050919050565b600060208201905081810360008301526120b68161207a565b9050919050565b60006120c8826117a1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036120fa576120f9611bf5565b5b600182019050919050565b7f56616c696461746f725365743a2063616e646964617465206973206e6f74207660008201527f616c696461746f72000000000000000000000000000000000000000000000000602082015250565b6000612161602883611ae6565b915061216c82612105565b604082019050919050565b6000602082019050818103600083015261219081612154565b9050919050565b7f56616c696461746f725365743a2063616e6e6f742072656d6f7665206c61737460008201527f2076616c696461746f7200000000000000000000000000000000000000000000602082015250565b60006121f3602a83611ae6565b91506121fe82612197565b604082019050919050565b60006020820190508181036000830152612222816121e6565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f56616c696461746f725365743a2070726f706f73616c20657865637574656400600082015250565b60006122bd601f83611ae6565b91506122c882612287565b602082019050919050565b600060208201905081810360008301526122ec816122b0565b9050919050565b7f56616c696461746f725365743a20616c726561647920766f7465640000000000600082015250565b6000612329601b83611ae6565b9150612334826122f3565b602082019050919050565b600060208201905081810360008301526123588161231c565b9050919050565b612368816117fa565b82525050565b6000606082019050612383600083018661235f565b61239060208301856117ab565b61239d60408301846117ab565b949350505050565b7f56616c696461746f725365743a207a65726f2076616c696461746f7200000000600082015250565b60006123db601c83611ae6565b91506123e6826123a5565b602082019050919050565b6000602082019050818103600083015261240a816123ce565b9050919050565b7f56616c696461746f725365743a206475706c69636174652076616c696461746f60008201527f7200000000000000000000000000000000000000000000000000000000000000602082015250565b600061246d602183611ae6565b915061247882612411565b604082019050919050565b6000602082019050818103600083015261249c81612460565b9050919050565b7f56616c696461746f725365743a2076616c696461746f72206e6f7420666f756e60008201527f6400000000000000000000000000000000000000000000000000000000000000602082015250565b60006124ff602183611ae6565b915061250a826124a3565b604082019050919050565b6000602082019050818103600083015261252e816124f2565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fdfea264697066735822122010ef9fcd63ea0d158ca96d87736aac0c3ac9911a27cfc09892a45b5805a9eed764736f6c634300081e0033" + +// DeployValidatorSet deploys a new Ethereum contract, binding an instance of ValidatorSet to it. +func DeployValidatorSet(auth *bind.TransactOpts, backend bind.ContractBackend, initialValidators []common.Address) (common.Address, *types.Transaction, *ValidatorSet, error) { + parsed, err := abi.JSON(strings.NewReader(ValidatorSetABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(ValidatorSetBin), backend, initialValidators) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &ValidatorSet{ValidatorSetCaller: ValidatorSetCaller{contract: contract}, ValidatorSetTransactor: ValidatorSetTransactor{contract: contract}, ValidatorSetFilterer: ValidatorSetFilterer{contract: contract}}, nil +} + +// ValidatorSet is an auto generated Go binding around an Ethereum contract. +type ValidatorSet struct { + ValidatorSetCaller // Read-only binding to the contract + ValidatorSetTransactor // Write-only binding to the contract + ValidatorSetFilterer // Log filterer for contract events +} + +// ValidatorSetCaller is an auto generated read-only Go binding around an Ethereum contract. +type ValidatorSetCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ValidatorSetTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ValidatorSetTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ValidatorSetFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ValidatorSetFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ValidatorSetSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ValidatorSetSession struct { + Contract *ValidatorSet // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ValidatorSetCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ValidatorSetCallerSession struct { + Contract *ValidatorSetCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ValidatorSetTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ValidatorSetTransactorSession struct { + Contract *ValidatorSetTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ValidatorSetRaw is an auto generated low-level Go binding around an Ethereum contract. +type ValidatorSetRaw struct { + Contract *ValidatorSet // Generic contract binding to access the raw methods on +} + +// ValidatorSetCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ValidatorSetCallerRaw struct { + Contract *ValidatorSetCaller // Generic read-only contract binding to access the raw methods on +} + +// ValidatorSetTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ValidatorSetTransactorRaw struct { + Contract *ValidatorSetTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewValidatorSet creates a new instance of ValidatorSet, bound to a specific deployed contract. +func NewValidatorSet(address common.Address, backend bind.ContractBackend) (*ValidatorSet, error) { + contract, err := bindValidatorSet(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &ValidatorSet{ValidatorSetCaller: ValidatorSetCaller{contract: contract}, ValidatorSetTransactor: ValidatorSetTransactor{contract: contract}, ValidatorSetFilterer: ValidatorSetFilterer{contract: contract}}, nil +} + +// NewValidatorSetCaller creates a new read-only instance of ValidatorSet, bound to a specific deployed contract. +func NewValidatorSetCaller(address common.Address, caller bind.ContractCaller) (*ValidatorSetCaller, error) { + contract, err := bindValidatorSet(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ValidatorSetCaller{contract: contract}, nil +} + +// NewValidatorSetTransactor creates a new write-only instance of ValidatorSet, bound to a specific deployed contract. +func NewValidatorSetTransactor(address common.Address, transactor bind.ContractTransactor) (*ValidatorSetTransactor, error) { + contract, err := bindValidatorSet(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ValidatorSetTransactor{contract: contract}, nil +} + +// NewValidatorSetFilterer creates a new log filterer instance of ValidatorSet, bound to a specific deployed contract. +func NewValidatorSetFilterer(address common.Address, filterer bind.ContractFilterer) (*ValidatorSetFilterer, error) { + contract, err := bindValidatorSet(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ValidatorSetFilterer{contract: contract}, nil +} + +// bindValidatorSet binds a generic wrapper to an already deployed contract. +func bindValidatorSet(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ValidatorSetABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ValidatorSet *ValidatorSetRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ValidatorSet.Contract.ValidatorSetCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ValidatorSet *ValidatorSetRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ValidatorSet.Contract.ValidatorSetTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ValidatorSet *ValidatorSetRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ValidatorSet.Contract.ValidatorSetTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ValidatorSet *ValidatorSetCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ValidatorSet.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ValidatorSet *ValidatorSetTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ValidatorSet.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ValidatorSet *ValidatorSetTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ValidatorSet.Contract.contract.Transact(opts, method, params...) +} + +// FaultTolerance is a free data retrieval call binding the contract method 0x75b76f1c. +// +// Solidity: function faultTolerance() view returns(uint256) +func (_ValidatorSet *ValidatorSetCaller) FaultTolerance(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _ValidatorSet.contract.Call(opts, &out, "faultTolerance") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// FaultTolerance is a free data retrieval call binding the contract method 0x75b76f1c. +// +// Solidity: function faultTolerance() view returns(uint256) +func (_ValidatorSet *ValidatorSetSession) FaultTolerance() (*big.Int, error) { + return _ValidatorSet.Contract.FaultTolerance(&_ValidatorSet.CallOpts) +} + +// FaultTolerance is a free data retrieval call binding the contract method 0x75b76f1c. +// +// Solidity: function faultTolerance() view returns(uint256) +func (_ValidatorSet *ValidatorSetCallerSession) FaultTolerance() (*big.Int, error) { + return _ValidatorSet.Contract.FaultTolerance(&_ValidatorSet.CallOpts) +} + +// GetAllValidators is a free data retrieval call binding the contract method 0xf3513a37. +// +// Solidity: function getAllValidators() view returns(address[]) +func (_ValidatorSet *ValidatorSetCaller) GetAllValidators(opts *bind.CallOpts) ([]common.Address, error) { + var out []interface{} + err := _ValidatorSet.contract.Call(opts, &out, "getAllValidators") + + if err != nil { + return *new([]common.Address), err + } + + out0 := *abi.ConvertType(out[0], new([]common.Address)).(*[]common.Address) + + return out0, err + +} + +// GetAllValidators is a free data retrieval call binding the contract method 0xf3513a37. +// +// Solidity: function getAllValidators() view returns(address[]) +func (_ValidatorSet *ValidatorSetSession) GetAllValidators() ([]common.Address, error) { + return _ValidatorSet.Contract.GetAllValidators(&_ValidatorSet.CallOpts) +} + +// GetAllValidators is a free data retrieval call binding the contract method 0xf3513a37. +// +// Solidity: function getAllValidators() view returns(address[]) +func (_ValidatorSet *ValidatorSetCallerSession) GetAllValidators() ([]common.Address, error) { + return _ValidatorSet.Contract.GetAllValidators(&_ValidatorSet.CallOpts) +} + +// GetValidators is a free data retrieval call binding the contract method 0xb7ab4db5. +// +// Solidity: function getValidators() view returns(address[]) +func (_ValidatorSet *ValidatorSetCaller) GetValidators(opts *bind.CallOpts) ([]common.Address, error) { + var out []interface{} + err := _ValidatorSet.contract.Call(opts, &out, "getValidators") + + if err != nil { + return *new([]common.Address), err + } + + out0 := *abi.ConvertType(out[0], new([]common.Address)).(*[]common.Address) + + return out0, err + +} + +// GetValidators is a free data retrieval call binding the contract method 0xb7ab4db5. +// +// Solidity: function getValidators() view returns(address[]) +func (_ValidatorSet *ValidatorSetSession) GetValidators() ([]common.Address, error) { + return _ValidatorSet.Contract.GetValidators(&_ValidatorSet.CallOpts) +} + +// GetValidators is a free data retrieval call binding the contract method 0xb7ab4db5. +// +// Solidity: function getValidators() view returns(address[]) +func (_ValidatorSet *ValidatorSetCallerSession) GetValidators() ([]common.Address, error) { + return _ValidatorSet.Contract.GetValidators(&_ValidatorSet.CallOpts) +} + +// HasVoted is a free data retrieval call binding the contract method 0x5e401b3e. +// +// Solidity: function hasVoted(uint8 operation, address candidate, address voter) view returns(bool) +func (_ValidatorSet *ValidatorSetCaller) HasVoted(opts *bind.CallOpts, operation uint8, candidate common.Address, voter common.Address) (bool, error) { + var out []interface{} + err := _ValidatorSet.contract.Call(opts, &out, "hasVoted", operation, candidate, voter) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// HasVoted is a free data retrieval call binding the contract method 0x5e401b3e. +// +// Solidity: function hasVoted(uint8 operation, address candidate, address voter) view returns(bool) +func (_ValidatorSet *ValidatorSetSession) HasVoted(operation uint8, candidate common.Address, voter common.Address) (bool, error) { + return _ValidatorSet.Contract.HasVoted(&_ValidatorSet.CallOpts, operation, candidate, voter) +} + +// HasVoted is a free data retrieval call binding the contract method 0x5e401b3e. +// +// Solidity: function hasVoted(uint8 operation, address candidate, address voter) view returns(bool) +func (_ValidatorSet *ValidatorSetCallerSession) HasVoted(operation uint8, candidate common.Address, voter common.Address) (bool, error) { + return _ValidatorSet.Contract.HasVoted(&_ValidatorSet.CallOpts, operation, candidate, voter) +} + +// IsInMaintenance is a free data retrieval call binding the contract method 0xdfa45bdd. +// +// Solidity: function isInMaintenance(address validator) view returns(bool) +func (_ValidatorSet *ValidatorSetCaller) IsInMaintenance(opts *bind.CallOpts, validator common.Address) (bool, error) { + var out []interface{} + err := _ValidatorSet.contract.Call(opts, &out, "isInMaintenance", validator) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsInMaintenance is a free data retrieval call binding the contract method 0xdfa45bdd. +// +// Solidity: function isInMaintenance(address validator) view returns(bool) +func (_ValidatorSet *ValidatorSetSession) IsInMaintenance(validator common.Address) (bool, error) { + return _ValidatorSet.Contract.IsInMaintenance(&_ValidatorSet.CallOpts, validator) +} + +// IsInMaintenance is a free data retrieval call binding the contract method 0xdfa45bdd. +// +// Solidity: function isInMaintenance(address validator) view returns(bool) +func (_ValidatorSet *ValidatorSetCallerSession) IsInMaintenance(validator common.Address) (bool, error) { + return _ValidatorSet.Contract.IsInMaintenance(&_ValidatorSet.CallOpts, validator) +} + +// IsValidator is a free data retrieval call binding the contract method 0xfacd743b. +// +// Solidity: function isValidator(address validator) view returns(bool) +func (_ValidatorSet *ValidatorSetCaller) IsValidator(opts *bind.CallOpts, validator common.Address) (bool, error) { + var out []interface{} + err := _ValidatorSet.contract.Call(opts, &out, "isValidator", validator) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsValidator is a free data retrieval call binding the contract method 0xfacd743b. +// +// Solidity: function isValidator(address validator) view returns(bool) +func (_ValidatorSet *ValidatorSetSession) IsValidator(validator common.Address) (bool, error) { + return _ValidatorSet.Contract.IsValidator(&_ValidatorSet.CallOpts, validator) +} + +// IsValidator is a free data retrieval call binding the contract method 0xfacd743b. +// +// Solidity: function isValidator(address validator) view returns(bool) +func (_ValidatorSet *ValidatorSetCallerSession) IsValidator(validator common.Address) (bool, error) { + return _ValidatorSet.Contract.IsValidator(&_ValidatorSet.CallOpts, validator) +} + +// MaintenanceSize is a free data retrieval call binding the contract method 0x22e28369. +// +// Solidity: function maintenanceSize() view returns(uint256) +func (_ValidatorSet *ValidatorSetCaller) MaintenanceSize(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _ValidatorSet.contract.Call(opts, &out, "maintenanceSize") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// MaintenanceSize is a free data retrieval call binding the contract method 0x22e28369. +// +// Solidity: function maintenanceSize() view returns(uint256) +func (_ValidatorSet *ValidatorSetSession) MaintenanceSize() (*big.Int, error) { + return _ValidatorSet.Contract.MaintenanceSize(&_ValidatorSet.CallOpts) +} + +// MaintenanceSize is a free data retrieval call binding the contract method 0x22e28369. +// +// Solidity: function maintenanceSize() view returns(uint256) +func (_ValidatorSet *ValidatorSetCallerSession) MaintenanceSize() (*big.Int, error) { + return _ValidatorSet.Contract.MaintenanceSize(&_ValidatorSet.CallOpts) +} + +// ProposalExecuted is a free data retrieval call binding the contract method 0xeac942a9. +// +// Solidity: function proposalExecuted(uint8 operation, address candidate) view returns(bool) +func (_ValidatorSet *ValidatorSetCaller) ProposalExecuted(opts *bind.CallOpts, operation uint8, candidate common.Address) (bool, error) { + var out []interface{} + err := _ValidatorSet.contract.Call(opts, &out, "proposalExecuted", operation, candidate) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// ProposalExecuted is a free data retrieval call binding the contract method 0xeac942a9. +// +// Solidity: function proposalExecuted(uint8 operation, address candidate) view returns(bool) +func (_ValidatorSet *ValidatorSetSession) ProposalExecuted(operation uint8, candidate common.Address) (bool, error) { + return _ValidatorSet.Contract.ProposalExecuted(&_ValidatorSet.CallOpts, operation, candidate) +} + +// ProposalExecuted is a free data retrieval call binding the contract method 0xeac942a9. +// +// Solidity: function proposalExecuted(uint8 operation, address candidate) view returns(bool) +func (_ValidatorSet *ValidatorSetCallerSession) ProposalExecuted(operation uint8, candidate common.Address) (bool, error) { + return _ValidatorSet.Contract.ProposalExecuted(&_ValidatorSet.CallOpts, operation, candidate) +} + +// ProposalId is a free data retrieval call binding the contract method 0xa8b4b210. +// +// Solidity: function proposalId(uint8 operation, address candidate) view returns(bytes32) +func (_ValidatorSet *ValidatorSetCaller) ProposalId(opts *bind.CallOpts, operation uint8, candidate common.Address) ([32]byte, error) { + var out []interface{} + err := _ValidatorSet.contract.Call(opts, &out, "proposalId", operation, candidate) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// ProposalId is a free data retrieval call binding the contract method 0xa8b4b210. +// +// Solidity: function proposalId(uint8 operation, address candidate) view returns(bytes32) +func (_ValidatorSet *ValidatorSetSession) ProposalId(operation uint8, candidate common.Address) ([32]byte, error) { + return _ValidatorSet.Contract.ProposalId(&_ValidatorSet.CallOpts, operation, candidate) +} + +// ProposalId is a free data retrieval call binding the contract method 0xa8b4b210. +// +// Solidity: function proposalId(uint8 operation, address candidate) view returns(bytes32) +func (_ValidatorSet *ValidatorSetCallerSession) ProposalId(operation uint8, candidate common.Address) ([32]byte, error) { + return _ValidatorSet.Contract.ProposalId(&_ValidatorSet.CallOpts, operation, candidate) +} + +// ProposalIdAtVersion is a free data retrieval call binding the contract method 0x58753136. +// +// Solidity: function proposalIdAtVersion(uint8 operation, address candidate, uint256 versionNumber) pure returns(bytes32) +func (_ValidatorSet *ValidatorSetCaller) ProposalIdAtVersion(opts *bind.CallOpts, operation uint8, candidate common.Address, versionNumber *big.Int) ([32]byte, error) { + var out []interface{} + err := _ValidatorSet.contract.Call(opts, &out, "proposalIdAtVersion", operation, candidate, versionNumber) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// ProposalIdAtVersion is a free data retrieval call binding the contract method 0x58753136. +// +// Solidity: function proposalIdAtVersion(uint8 operation, address candidate, uint256 versionNumber) pure returns(bytes32) +func (_ValidatorSet *ValidatorSetSession) ProposalIdAtVersion(operation uint8, candidate common.Address, versionNumber *big.Int) ([32]byte, error) { + return _ValidatorSet.Contract.ProposalIdAtVersion(&_ValidatorSet.CallOpts, operation, candidate, versionNumber) +} + +// ProposalIdAtVersion is a free data retrieval call binding the contract method 0x58753136. +// +// Solidity: function proposalIdAtVersion(uint8 operation, address candidate, uint256 versionNumber) pure returns(bytes32) +func (_ValidatorSet *ValidatorSetCallerSession) ProposalIdAtVersion(operation uint8, candidate common.Address, versionNumber *big.Int) ([32]byte, error) { + return _ValidatorSet.Contract.ProposalIdAtVersion(&_ValidatorSet.CallOpts, operation, candidate, versionNumber) +} + +// ProposalVotes is a free data retrieval call binding the contract method 0xe6c166e3. +// +// Solidity: function proposalVotes(uint8 operation, address candidate) view returns(uint256) +func (_ValidatorSet *ValidatorSetCaller) ProposalVotes(opts *bind.CallOpts, operation uint8, candidate common.Address) (*big.Int, error) { + var out []interface{} + err := _ValidatorSet.contract.Call(opts, &out, "proposalVotes", operation, candidate) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// ProposalVotes is a free data retrieval call binding the contract method 0xe6c166e3. +// +// Solidity: function proposalVotes(uint8 operation, address candidate) view returns(uint256) +func (_ValidatorSet *ValidatorSetSession) ProposalVotes(operation uint8, candidate common.Address) (*big.Int, error) { + return _ValidatorSet.Contract.ProposalVotes(&_ValidatorSet.CallOpts, operation, candidate) +} + +// ProposalVotes is a free data retrieval call binding the contract method 0xe6c166e3. +// +// Solidity: function proposalVotes(uint8 operation, address candidate) view returns(uint256) +func (_ValidatorSet *ValidatorSetCallerSession) ProposalVotes(operation uint8, candidate common.Address) (*big.Int, error) { + return _ValidatorSet.Contract.ProposalVotes(&_ValidatorSet.CallOpts, operation, candidate) +} + +// QuorumSize is a free data retrieval call binding the contract method 0x3068ef4d. +// +// Solidity: function quorumSize() view returns(uint256) +func (_ValidatorSet *ValidatorSetCaller) QuorumSize(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _ValidatorSet.contract.Call(opts, &out, "quorumSize") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// QuorumSize is a free data retrieval call binding the contract method 0x3068ef4d. +// +// Solidity: function quorumSize() view returns(uint256) +func (_ValidatorSet *ValidatorSetSession) QuorumSize() (*big.Int, error) { + return _ValidatorSet.Contract.QuorumSize(&_ValidatorSet.CallOpts) +} + +// QuorumSize is a free data retrieval call binding the contract method 0x3068ef4d. +// +// Solidity: function quorumSize() view returns(uint256) +func (_ValidatorSet *ValidatorSetCallerSession) QuorumSize() (*big.Int, error) { + return _ValidatorSet.Contract.QuorumSize(&_ValidatorSet.CallOpts) +} + +// Version is a free data retrieval call binding the contract method 0x54fd4d50. +// +// Solidity: function version() view returns(uint256) +func (_ValidatorSet *ValidatorSetCaller) Version(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _ValidatorSet.contract.Call(opts, &out, "version") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// Version is a free data retrieval call binding the contract method 0x54fd4d50. +// +// Solidity: function version() view returns(uint256) +func (_ValidatorSet *ValidatorSetSession) Version() (*big.Int, error) { + return _ValidatorSet.Contract.Version(&_ValidatorSet.CallOpts) +} + +// Version is a free data retrieval call binding the contract method 0x54fd4d50. +// +// Solidity: function version() view returns(uint256) +func (_ValidatorSet *ValidatorSetCallerSession) Version() (*big.Int, error) { + return _ValidatorSet.Contract.Version(&_ValidatorSet.CallOpts) +} + +// EnterMaintenance is a paid mutator transaction binding the contract method 0x9369d7de. +// +// Solidity: function enterMaintenance() returns() +func (_ValidatorSet *ValidatorSetTransactor) EnterMaintenance(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ValidatorSet.contract.Transact(opts, "enterMaintenance") +} + +// EnterMaintenance is a paid mutator transaction binding the contract method 0x9369d7de. +// +// Solidity: function enterMaintenance() returns() +func (_ValidatorSet *ValidatorSetSession) EnterMaintenance() (*types.Transaction, error) { + return _ValidatorSet.Contract.EnterMaintenance(&_ValidatorSet.TransactOpts) +} + +// EnterMaintenance is a paid mutator transaction binding the contract method 0x9369d7de. +// +// Solidity: function enterMaintenance() returns() +func (_ValidatorSet *ValidatorSetTransactorSession) EnterMaintenance() (*types.Transaction, error) { + return _ValidatorSet.Contract.EnterMaintenance(&_ValidatorSet.TransactOpts) +} + +// ExitMaintenance is a paid mutator transaction binding the contract method 0x04c4fec6. +// +// Solidity: function exitMaintenance() returns() +func (_ValidatorSet *ValidatorSetTransactor) ExitMaintenance(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ValidatorSet.contract.Transact(opts, "exitMaintenance") +} + +// ExitMaintenance is a paid mutator transaction binding the contract method 0x04c4fec6. +// +// Solidity: function exitMaintenance() returns() +func (_ValidatorSet *ValidatorSetSession) ExitMaintenance() (*types.Transaction, error) { + return _ValidatorSet.Contract.ExitMaintenance(&_ValidatorSet.TransactOpts) +} + +// ExitMaintenance is a paid mutator transaction binding the contract method 0x04c4fec6. +// +// Solidity: function exitMaintenance() returns() +func (_ValidatorSet *ValidatorSetTransactorSession) ExitMaintenance() (*types.Transaction, error) { + return _ValidatorSet.Contract.ExitMaintenance(&_ValidatorSet.TransactOpts) +} + +// VoteAddValidator is a paid mutator transaction binding the contract method 0x57456f22. +// +// Solidity: function voteAddValidator(address candidate) returns() +func (_ValidatorSet *ValidatorSetTransactor) VoteAddValidator(opts *bind.TransactOpts, candidate common.Address) (*types.Transaction, error) { + return _ValidatorSet.contract.Transact(opts, "voteAddValidator", candidate) +} + +// VoteAddValidator is a paid mutator transaction binding the contract method 0x57456f22. +// +// Solidity: function voteAddValidator(address candidate) returns() +func (_ValidatorSet *ValidatorSetSession) VoteAddValidator(candidate common.Address) (*types.Transaction, error) { + return _ValidatorSet.Contract.VoteAddValidator(&_ValidatorSet.TransactOpts, candidate) +} + +// VoteAddValidator is a paid mutator transaction binding the contract method 0x57456f22. +// +// Solidity: function voteAddValidator(address candidate) returns() +func (_ValidatorSet *ValidatorSetTransactorSession) VoteAddValidator(candidate common.Address) (*types.Transaction, error) { + return _ValidatorSet.Contract.VoteAddValidator(&_ValidatorSet.TransactOpts, candidate) +} + +// VoteRemoveValidator is a paid mutator transaction binding the contract method 0x9bf6a74a. +// +// Solidity: function voteRemoveValidator(address candidate) returns() +func (_ValidatorSet *ValidatorSetTransactor) VoteRemoveValidator(opts *bind.TransactOpts, candidate common.Address) (*types.Transaction, error) { + return _ValidatorSet.contract.Transact(opts, "voteRemoveValidator", candidate) +} + +// VoteRemoveValidator is a paid mutator transaction binding the contract method 0x9bf6a74a. +// +// Solidity: function voteRemoveValidator(address candidate) returns() +func (_ValidatorSet *ValidatorSetSession) VoteRemoveValidator(candidate common.Address) (*types.Transaction, error) { + return _ValidatorSet.Contract.VoteRemoveValidator(&_ValidatorSet.TransactOpts, candidate) +} + +// VoteRemoveValidator is a paid mutator transaction binding the contract method 0x9bf6a74a. +// +// Solidity: function voteRemoveValidator(address candidate) returns() +func (_ValidatorSet *ValidatorSetTransactorSession) VoteRemoveValidator(candidate common.Address) (*types.Transaction, error) { + return _ValidatorSet.Contract.VoteRemoveValidator(&_ValidatorSet.TransactOpts, candidate) +} + +// ValidatorSetMaintenanceEnteredIterator is returned from FilterMaintenanceEntered and is used to iterate over the raw logs and unpacked data for MaintenanceEntered events raised by the ValidatorSet contract. +type ValidatorSetMaintenanceEnteredIterator struct { + Event *ValidatorSetMaintenanceEntered // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ValidatorSetMaintenanceEnteredIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ValidatorSetMaintenanceEntered) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ValidatorSetMaintenanceEntered) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ValidatorSetMaintenanceEnteredIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ValidatorSetMaintenanceEnteredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ValidatorSetMaintenanceEntered represents a MaintenanceEntered event raised by the ValidatorSet contract. +type ValidatorSetMaintenanceEntered struct { + Validator common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterMaintenanceEntered is a free log retrieval operation binding the contract event 0x44aa86acf916265e97ff989ab2c2289b9f442a5a4769121a3c82d3daf7ea972b. +// +// Solidity: event MaintenanceEntered(address indexed validator) +func (_ValidatorSet *ValidatorSetFilterer) FilterMaintenanceEntered(opts *bind.FilterOpts, validator []common.Address) (*ValidatorSetMaintenanceEnteredIterator, error) { + + var validatorRule []interface{} + for _, validatorItem := range validator { + validatorRule = append(validatorRule, validatorItem) + } + + logs, sub, err := _ValidatorSet.contract.FilterLogs(opts, "MaintenanceEntered", validatorRule) + if err != nil { + return nil, err + } + return &ValidatorSetMaintenanceEnteredIterator{contract: _ValidatorSet.contract, event: "MaintenanceEntered", logs: logs, sub: sub}, nil +} + +var MaintenanceEnteredTopicHash = "0x44aa86acf916265e97ff989ab2c2289b9f442a5a4769121a3c82d3daf7ea972b" + +// WatchMaintenanceEntered is a free log subscription operation binding the contract event 0x44aa86acf916265e97ff989ab2c2289b9f442a5a4769121a3c82d3daf7ea972b. +// +// Solidity: event MaintenanceEntered(address indexed validator) +func (_ValidatorSet *ValidatorSetFilterer) WatchMaintenanceEntered(opts *bind.WatchOpts, sink chan<- *ValidatorSetMaintenanceEntered, validator []common.Address) (event.Subscription, error) { + + var validatorRule []interface{} + for _, validatorItem := range validator { + validatorRule = append(validatorRule, validatorItem) + } + + logs, sub, err := _ValidatorSet.contract.WatchLogs(opts, "MaintenanceEntered", validatorRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ValidatorSetMaintenanceEntered) + if err := _ValidatorSet.contract.UnpackLog(event, "MaintenanceEntered", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseMaintenanceEntered is a log parse operation binding the contract event 0x44aa86acf916265e97ff989ab2c2289b9f442a5a4769121a3c82d3daf7ea972b. +// +// Solidity: event MaintenanceEntered(address indexed validator) +func (_ValidatorSet *ValidatorSetFilterer) ParseMaintenanceEntered(log types.Log) (*ValidatorSetMaintenanceEntered, error) { + event := new(ValidatorSetMaintenanceEntered) + if err := _ValidatorSet.contract.UnpackLog(event, "MaintenanceEntered", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ValidatorSetMaintenanceExitedIterator is returned from FilterMaintenanceExited and is used to iterate over the raw logs and unpacked data for MaintenanceExited events raised by the ValidatorSet contract. +type ValidatorSetMaintenanceExitedIterator struct { + Event *ValidatorSetMaintenanceExited // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ValidatorSetMaintenanceExitedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ValidatorSetMaintenanceExited) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ValidatorSetMaintenanceExited) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ValidatorSetMaintenanceExitedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ValidatorSetMaintenanceExitedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ValidatorSetMaintenanceExited represents a MaintenanceExited event raised by the ValidatorSet contract. +type ValidatorSetMaintenanceExited struct { + Validator common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterMaintenanceExited is a free log retrieval operation binding the contract event 0x0203ae0082a4b3f0b71e461ab5245258799bdb5d52d0f224a6aaed75deca6889. +// +// Solidity: event MaintenanceExited(address indexed validator) +func (_ValidatorSet *ValidatorSetFilterer) FilterMaintenanceExited(opts *bind.FilterOpts, validator []common.Address) (*ValidatorSetMaintenanceExitedIterator, error) { + + var validatorRule []interface{} + for _, validatorItem := range validator { + validatorRule = append(validatorRule, validatorItem) + } + + logs, sub, err := _ValidatorSet.contract.FilterLogs(opts, "MaintenanceExited", validatorRule) + if err != nil { + return nil, err + } + return &ValidatorSetMaintenanceExitedIterator{contract: _ValidatorSet.contract, event: "MaintenanceExited", logs: logs, sub: sub}, nil +} + +var MaintenanceExitedTopicHash = "0x0203ae0082a4b3f0b71e461ab5245258799bdb5d52d0f224a6aaed75deca6889" + +// WatchMaintenanceExited is a free log subscription operation binding the contract event 0x0203ae0082a4b3f0b71e461ab5245258799bdb5d52d0f224a6aaed75deca6889. +// +// Solidity: event MaintenanceExited(address indexed validator) +func (_ValidatorSet *ValidatorSetFilterer) WatchMaintenanceExited(opts *bind.WatchOpts, sink chan<- *ValidatorSetMaintenanceExited, validator []common.Address) (event.Subscription, error) { + + var validatorRule []interface{} + for _, validatorItem := range validator { + validatorRule = append(validatorRule, validatorItem) + } + + logs, sub, err := _ValidatorSet.contract.WatchLogs(opts, "MaintenanceExited", validatorRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ValidatorSetMaintenanceExited) + if err := _ValidatorSet.contract.UnpackLog(event, "MaintenanceExited", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseMaintenanceExited is a log parse operation binding the contract event 0x0203ae0082a4b3f0b71e461ab5245258799bdb5d52d0f224a6aaed75deca6889. +// +// Solidity: event MaintenanceExited(address indexed validator) +func (_ValidatorSet *ValidatorSetFilterer) ParseMaintenanceExited(log types.Log) (*ValidatorSetMaintenanceExited, error) { + event := new(ValidatorSetMaintenanceExited) + if err := _ValidatorSet.contract.UnpackLog(event, "MaintenanceExited", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ValidatorSetValidatorAddedIterator is returned from FilterValidatorAdded and is used to iterate over the raw logs and unpacked data for ValidatorAdded events raised by the ValidatorSet contract. +type ValidatorSetValidatorAddedIterator struct { + Event *ValidatorSetValidatorAdded // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ValidatorSetValidatorAddedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ValidatorSetValidatorAdded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ValidatorSetValidatorAdded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ValidatorSetValidatorAddedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ValidatorSetValidatorAddedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ValidatorSetValidatorAdded represents a ValidatorAdded event raised by the ValidatorSet contract. +type ValidatorSetValidatorAdded struct { + Validator common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterValidatorAdded is a free log retrieval operation binding the contract event 0xe366c1c0452ed8eec96861e9e54141ebff23c9ec89fe27b996b45f5ec3884987. +// +// Solidity: event ValidatorAdded(address indexed validator) +func (_ValidatorSet *ValidatorSetFilterer) FilterValidatorAdded(opts *bind.FilterOpts, validator []common.Address) (*ValidatorSetValidatorAddedIterator, error) { + + var validatorRule []interface{} + for _, validatorItem := range validator { + validatorRule = append(validatorRule, validatorItem) + } + + logs, sub, err := _ValidatorSet.contract.FilterLogs(opts, "ValidatorAdded", validatorRule) + if err != nil { + return nil, err + } + return &ValidatorSetValidatorAddedIterator{contract: _ValidatorSet.contract, event: "ValidatorAdded", logs: logs, sub: sub}, nil +} + +var ValidatorAddedTopicHash = "0xe366c1c0452ed8eec96861e9e54141ebff23c9ec89fe27b996b45f5ec3884987" + +// WatchValidatorAdded is a free log subscription operation binding the contract event 0xe366c1c0452ed8eec96861e9e54141ebff23c9ec89fe27b996b45f5ec3884987. +// +// Solidity: event ValidatorAdded(address indexed validator) +func (_ValidatorSet *ValidatorSetFilterer) WatchValidatorAdded(opts *bind.WatchOpts, sink chan<- *ValidatorSetValidatorAdded, validator []common.Address) (event.Subscription, error) { + + var validatorRule []interface{} + for _, validatorItem := range validator { + validatorRule = append(validatorRule, validatorItem) + } + + logs, sub, err := _ValidatorSet.contract.WatchLogs(opts, "ValidatorAdded", validatorRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ValidatorSetValidatorAdded) + if err := _ValidatorSet.contract.UnpackLog(event, "ValidatorAdded", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseValidatorAdded is a log parse operation binding the contract event 0xe366c1c0452ed8eec96861e9e54141ebff23c9ec89fe27b996b45f5ec3884987. +// +// Solidity: event ValidatorAdded(address indexed validator) +func (_ValidatorSet *ValidatorSetFilterer) ParseValidatorAdded(log types.Log) (*ValidatorSetValidatorAdded, error) { + event := new(ValidatorSetValidatorAdded) + if err := _ValidatorSet.contract.UnpackLog(event, "ValidatorAdded", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ValidatorSetValidatorRemovedIterator is returned from FilterValidatorRemoved and is used to iterate over the raw logs and unpacked data for ValidatorRemoved events raised by the ValidatorSet contract. +type ValidatorSetValidatorRemovedIterator struct { + Event *ValidatorSetValidatorRemoved // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ValidatorSetValidatorRemovedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ValidatorSetValidatorRemoved) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ValidatorSetValidatorRemoved) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ValidatorSetValidatorRemovedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ValidatorSetValidatorRemovedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ValidatorSetValidatorRemoved represents a ValidatorRemoved event raised by the ValidatorSet contract. +type ValidatorSetValidatorRemoved struct { + Validator common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterValidatorRemoved is a free log retrieval operation binding the contract event 0xe1434e25d6611e0db941968fdc97811c982ac1602e951637d206f5fdda9dd8f1. +// +// Solidity: event ValidatorRemoved(address indexed validator) +func (_ValidatorSet *ValidatorSetFilterer) FilterValidatorRemoved(opts *bind.FilterOpts, validator []common.Address) (*ValidatorSetValidatorRemovedIterator, error) { + + var validatorRule []interface{} + for _, validatorItem := range validator { + validatorRule = append(validatorRule, validatorItem) + } + + logs, sub, err := _ValidatorSet.contract.FilterLogs(opts, "ValidatorRemoved", validatorRule) + if err != nil { + return nil, err + } + return &ValidatorSetValidatorRemovedIterator{contract: _ValidatorSet.contract, event: "ValidatorRemoved", logs: logs, sub: sub}, nil +} + +var ValidatorRemovedTopicHash = "0xe1434e25d6611e0db941968fdc97811c982ac1602e951637d206f5fdda9dd8f1" + +// WatchValidatorRemoved is a free log subscription operation binding the contract event 0xe1434e25d6611e0db941968fdc97811c982ac1602e951637d206f5fdda9dd8f1. +// +// Solidity: event ValidatorRemoved(address indexed validator) +func (_ValidatorSet *ValidatorSetFilterer) WatchValidatorRemoved(opts *bind.WatchOpts, sink chan<- *ValidatorSetValidatorRemoved, validator []common.Address) (event.Subscription, error) { + + var validatorRule []interface{} + for _, validatorItem := range validator { + validatorRule = append(validatorRule, validatorItem) + } + + logs, sub, err := _ValidatorSet.contract.WatchLogs(opts, "ValidatorRemoved", validatorRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ValidatorSetValidatorRemoved) + if err := _ValidatorSet.contract.UnpackLog(event, "ValidatorRemoved", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseValidatorRemoved is a log parse operation binding the contract event 0xe1434e25d6611e0db941968fdc97811c982ac1602e951637d206f5fdda9dd8f1. +// +// Solidity: event ValidatorRemoved(address indexed validator) +func (_ValidatorSet *ValidatorSetFilterer) ParseValidatorRemoved(log types.Log) (*ValidatorSetValidatorRemoved, error) { + event := new(ValidatorSetValidatorRemoved) + if err := _ValidatorSet.contract.UnpackLog(event, "ValidatorRemoved", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ValidatorSetValidatorVoteIterator is returned from FilterValidatorVote and is used to iterate over the raw logs and unpacked data for ValidatorVote events raised by the ValidatorSet contract. +type ValidatorSetValidatorVoteIterator struct { + Event *ValidatorSetValidatorVote // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ValidatorSetValidatorVoteIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ValidatorSetValidatorVote) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ValidatorSetValidatorVote) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ValidatorSetValidatorVoteIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ValidatorSetValidatorVoteIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ValidatorSetValidatorVote represents a ValidatorVote event raised by the ValidatorSet contract. +type ValidatorSetValidatorVote struct { + ProposalId [32]byte + Operation uint8 + Candidate common.Address + Voter common.Address + Votes *big.Int + Quorum *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterValidatorVote is a free log retrieval operation binding the contract event 0x88de5c1b4216be3907eda6a00c98004ce4a2076ccd207f384d40e92d288fdab6. +// +// Solidity: event ValidatorVote(bytes32 indexed proposalId, uint8 indexed operation, address indexed candidate, address voter, uint256 votes, uint256 quorum) +func (_ValidatorSet *ValidatorSetFilterer) FilterValidatorVote(opts *bind.FilterOpts, proposalId [][32]byte, operation []uint8, candidate []common.Address) (*ValidatorSetValidatorVoteIterator, error) { + + var proposalIdRule []interface{} + for _, proposalIdItem := range proposalId { + proposalIdRule = append(proposalIdRule, proposalIdItem) + } + var operationRule []interface{} + for _, operationItem := range operation { + operationRule = append(operationRule, operationItem) + } + var candidateRule []interface{} + for _, candidateItem := range candidate { + candidateRule = append(candidateRule, candidateItem) + } + + logs, sub, err := _ValidatorSet.contract.FilterLogs(opts, "ValidatorVote", proposalIdRule, operationRule, candidateRule) + if err != nil { + return nil, err + } + return &ValidatorSetValidatorVoteIterator{contract: _ValidatorSet.contract, event: "ValidatorVote", logs: logs, sub: sub}, nil +} + +var ValidatorVoteTopicHash = "0x88de5c1b4216be3907eda6a00c98004ce4a2076ccd207f384d40e92d288fdab6" + +// WatchValidatorVote is a free log subscription operation binding the contract event 0x88de5c1b4216be3907eda6a00c98004ce4a2076ccd207f384d40e92d288fdab6. +// +// Solidity: event ValidatorVote(bytes32 indexed proposalId, uint8 indexed operation, address indexed candidate, address voter, uint256 votes, uint256 quorum) +func (_ValidatorSet *ValidatorSetFilterer) WatchValidatorVote(opts *bind.WatchOpts, sink chan<- *ValidatorSetValidatorVote, proposalId [][32]byte, operation []uint8, candidate []common.Address) (event.Subscription, error) { + + var proposalIdRule []interface{} + for _, proposalIdItem := range proposalId { + proposalIdRule = append(proposalIdRule, proposalIdItem) + } + var operationRule []interface{} + for _, operationItem := range operation { + operationRule = append(operationRule, operationItem) + } + var candidateRule []interface{} + for _, candidateItem := range candidate { + candidateRule = append(candidateRule, candidateItem) + } + + logs, sub, err := _ValidatorSet.contract.WatchLogs(opts, "ValidatorVote", proposalIdRule, operationRule, candidateRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ValidatorSetValidatorVote) + if err := _ValidatorSet.contract.UnpackLog(event, "ValidatorVote", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseValidatorVote is a log parse operation binding the contract event 0x88de5c1b4216be3907eda6a00c98004ce4a2076ccd207f384d40e92d288fdab6. +// +// Solidity: event ValidatorVote(bytes32 indexed proposalId, uint8 indexed operation, address indexed candidate, address voter, uint256 votes, uint256 quorum) +func (_ValidatorSet *ValidatorSetFilterer) ParseValidatorVote(log types.Log) (*ValidatorSetValidatorVote, error) { + event := new(ValidatorSetValidatorVote) + if err := _ValidatorSet.contract.UnpackLog(event, "ValidatorVote", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/contracts/validatorcontract/contract/validator_set.sol b/contracts/validatorcontract/contract/validator_set.sol new file mode 100644 index 000000000..c0e13eb17 --- /dev/null +++ b/contracts/validatorcontract/contract/validator_set.sol @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2026 BOOSTRY Co., Ltd. +pragma solidity ^0.8.30; + +/// @title ValidatorSet +/// @notice Reference validator contract for QBFT contract-based validator selection. +/// @dev +/// This contract exposes `getValidators()` as the canonical read interface used by +/// QBFT contract-based validator selection. The returned validator set excludes +/// validators that have put themselves into maintenance mode, while governance +/// voting power remains based on the full registered validator set. +contract ValidatorSet { + /// @notice Governance operation type for validator set proposals. + /// @dev Values are part of the proposal ID preimage. Do not reorder them in upgrades. + enum Operation { + Add, + Remove + } + + /// @dev Tracks votes for a single operation/candidate pair. + /// The nested `voted` mapping prevents duplicate votes by the same validator. + struct Proposal { + uint256 votes; + bool executed; + mapping(address => bool) voted; + } + + /// @notice Emitted when a candidate reaches quorum and is added as a validator. + /// @param validator The validator address added to the registered validator set. + event ValidatorAdded(address indexed validator); + + /// @notice Emitted when a candidate reaches quorum and is removed from the validator set. + /// @param validator The validator address removed from the registered validator set. + event ValidatorRemoved(address indexed validator); + + /// @notice Emitted whenever a validator casts a governance vote. + /// @param proposalId Unique ID derived from the operation, candidate, and validator set version. + /// @param operation The requested validator set operation. + /// @param candidate The address proposed for addition or removal. + /// @param voter The validator that cast the vote. + /// @param votes Current number of votes recorded for the proposal after this vote. + /// @param quorum Number of votes required for the proposal to execute. + event ValidatorVote( + bytes32 indexed proposalId, + Operation indexed operation, + address indexed candidate, + address voter, + uint256 votes, + uint256 quorum + ); + + /// @notice Emitted when a validator enters maintenance mode. + /// @param validator The validator excluded from `getValidators()` while in maintenance. + event MaintenanceEntered(address indexed validator); + + /// @notice Emitted when a validator exits maintenance mode. + /// @param validator The validator included again in `getValidators()`. + event MaintenanceExited(address indexed validator); + + address[] private validators; + mapping(address => bool) private validatorStatus; + mapping(address => uint256) private validatorIndexes; + mapping(address => bool) private maintenanceStatus; + uint256 private maintenanceCount; + uint256 private validatorSetVersion; + mapping(bytes32 => Proposal) private proposals; + + modifier onlyValidator() { + require(validatorStatus[msg.sender], "ValidatorSet: sender is not validator"); + _; + } + + /// @notice Deploys the validator set with the initial registered validators. + /// @dev + /// The initial list must be non-empty, contain no zero address, and contain no + /// duplicate addresses. All initial validators are active at deployment. + /// @param initialValidators Initial registered validator addresses. + constructor(address[] memory initialValidators) { + require(initialValidators.length > 0, "ValidatorSet: empty validators"); + for (uint256 i = 0; i < initialValidators.length; i++) { + _addValidator(initialValidators[i]); + } + } + + /// @notice Returns active validators for QBFT proposer selection and quorum calculation. + /// @dev + /// This is the compatibility method expected by the existing QBFT contract mode. + /// Validators in maintenance are deliberately excluded from this returned list. + /// The returned order follows the registered validator order, skipping maintenance entries. + /// @return Active validator addresses. + function getValidators() external view returns (address[] memory) { + uint256 activeCount = validators.length - maintenanceCount; + address[] memory activeValidators = new address[](activeCount); + uint256 index = 0; + + for (uint256 i = 0; i < validators.length; i++) { + address validator = validators[i]; + if (!maintenanceStatus[validator]) { + activeValidators[index] = validator; + index++; + } + } + + return activeValidators; + } + + /// @notice Returns the full registered validator set. + /// @dev Includes validators currently in maintenance. + /// @return All registered validator addresses in registration order. + function getAllValidators() external view returns (address[] memory) { + address[] memory allValidators = new address[](validators.length); + for (uint256 i = 0; i < validators.length; i++) { + allValidators[i] = validators[i]; + } + return allValidators; + } + + /// @notice Checks whether an address is a registered validator. + /// @param validator Address to check. + /// @return True if the address is registered as a validator. + function isValidator(address validator) external view returns (bool) { + return validatorStatus[validator]; + } + + /// @notice Checks whether a validator is currently in maintenance. + /// @param validator Address to check. + /// @return True if the address is flagged as in maintenance. + function isInMaintenance(address validator) external view returns (bool) { + return maintenanceStatus[validator]; + } + + /// @notice Returns the tolerated Byzantine fault count `f`. + /// @dev Calculated from the full registered validator count as `(N - 1) / 3`. + /// @return Number of faulty validators tolerated by the current registered set size. + function faultTolerance() public view returns (uint256) { + return (validators.length - 1) / 3; + } + + /// @notice Returns the validator governance quorum. + /// @dev Uses the QBFT-style threshold `2f + 1`, where `f` is based on registered validators. + /// @return Number of votes required to execute add/remove proposals. + function quorumSize() public view returns (uint256) { + return (2 * faultTolerance()) + 1; + } + + /// @notice Returns the number of validators currently in maintenance. + /// @return Current maintenance validator count. + function maintenanceSize() external view returns (uint256) { + return maintenanceCount; + } + + /// @notice Returns the current validator set version. + /// @dev The version is incremented each time an add/remove proposal executes. + /// @return Current validator set version. + function version() external view returns (uint256) { + return validatorSetVersion; + } + + /// @notice Computes the proposal ID for a validator set operation. + /// @dev The same operation/candidate pair maps to the same proposal for all voters within the current validator set version. + /// @param operation Validator set operation type. + /// @param candidate Candidate address proposed for addition or removal. + /// @return Unique proposal ID for the current validator set version. + function proposalId(Operation operation, address candidate) public view returns (bytes32) { + return _currentProposalId(operation, candidate); + } + + /// @notice Computes the proposal ID for a validator set operation at a specific version. + /// @param operation Validator set operation type. + /// @param candidate Candidate address proposed for addition or removal. + /// @param versionNumber Validator set version to include in the proposal ID. + /// @return Unique proposal ID for the operation, candidate, and version. + function proposalIdAtVersion(Operation operation, address candidate, uint256 versionNumber) public pure returns (bytes32) { + return keccak256(abi.encodePacked(operation, candidate, versionNumber)); + } + + /// @notice Returns the number of votes recorded for a proposal. + /// @param operation Validator set operation type. + /// @param candidate Candidate address proposed for addition or removal. + /// @return Number of votes recorded for the proposal. + function proposalVotes(Operation operation, address candidate) external view returns (uint256) { + return proposals[_currentProposalId(operation, candidate)].votes; + } + + /// @notice Checks whether a proposal has already executed. + /// @param operation Validator set operation type. + /// @param candidate Candidate address proposed for addition or removal. + /// @return True if the proposal reached quorum and executed. + function proposalExecuted(Operation operation, address candidate) external view returns (bool) { + return proposals[_currentProposalId(operation, candidate)].executed; + } + + /// @notice Checks whether a validator has voted for a proposal. + /// @param operation Validator set operation type. + /// @param candidate Candidate address proposed for addition or removal. + /// @param voter Validator address to check. + /// @return True if `voter` has already voted for the proposal. + function hasVoted(Operation operation, address candidate, address voter) external view returns (bool) { + return proposals[_currentProposalId(operation, candidate)].voted[voter]; + } + + /// @notice Votes to add a new validator. + /// @dev + /// Only registered validators may vote. The candidate must not already be a validator. + /// When the proposal reaches `quorumSize()`, the candidate is added immediately. + /// @param candidate Address proposed as a new validator. + function voteAddValidator(address candidate) external onlyValidator { + require(candidate != address(0), "ValidatorSet: zero candidate"); + require(!validatorStatus[candidate], "ValidatorSet: candidate is validator"); + + _vote(Operation.Add, candidate); + } + + /// @notice Votes to remove an existing validator. + /// @dev + /// Only registered validators may vote. The candidate must currently be registered. + /// When the proposal reaches `quorumSize()`, the candidate is removed immediately. + /// If the candidate is in maintenance, the maintenance count is decremented as part of removal. + /// @param candidate Existing validator proposed for removal. + function voteRemoveValidator(address candidate) external onlyValidator { + require(validatorStatus[candidate], "ValidatorSet: candidate is not validator"); + require(validators.length > 1, "ValidatorSet: cannot remove last validator"); + + _vote(Operation.Remove, candidate); + } + + /// @notice Puts the caller into maintenance mode. + /// @dev + /// Maintenance mode excludes the caller from `getValidators()` but does not remove + /// governance voting rights. At most `faultTolerance()` validators may be in + /// maintenance at the same time. + function enterMaintenance() external onlyValidator { + require(!maintenanceStatus[msg.sender], "ValidatorSet: already in maintenance"); + require(maintenanceCount < faultTolerance(), "ValidatorSet: too many validators in maintenance"); + + maintenanceStatus[msg.sender] = true; + maintenanceCount++; + + emit MaintenanceEntered(msg.sender); + } + + /// @notice Removes the caller from maintenance mode. + /// @dev The caller becomes visible through `getValidators()` again after this call. + function exitMaintenance() external onlyValidator { + require(maintenanceStatus[msg.sender], "ValidatorSet: not in maintenance"); + + maintenanceStatus[msg.sender] = false; + maintenanceCount--; + + emit MaintenanceExited(msg.sender); + } + + /// @dev Records a validator vote and executes the proposal immediately once quorum is reached. + /// Reverts if the proposal already executed or the caller already voted for it. + function _vote(Operation operation, address candidate) private { + bytes32 id = _currentProposalId(operation, candidate); + Proposal storage proposal = proposals[id]; + require(!proposal.executed, "ValidatorSet: proposal executed"); + require(!proposal.voted[msg.sender], "ValidatorSet: already voted"); + + proposal.voted[msg.sender] = true; + proposal.votes++; + + uint256 quorum = quorumSize(); + emit ValidatorVote(id, operation, candidate, msg.sender, proposal.votes, quorum); + + if (proposal.votes >= quorum) { + proposal.executed = true; + if (operation == Operation.Add) { + _addValidator(candidate); + emit ValidatorAdded(candidate); + } else { + _removeValidator(candidate); + emit ValidatorRemoved(candidate); + } + validatorSetVersion++; + } + } + + /// @dev Computes a proposal ID for the current validator set version. + function _currentProposalId(Operation operation, address candidate) private view returns (bytes32) { + return proposalIdAtVersion(operation, candidate, validatorSetVersion); + } + + /// @dev Adds a validator to the registered set and preserves registration order. + /// Reverts on zero address or duplicate registration. + function _addValidator(address validator) private { + require(validator != address(0), "ValidatorSet: zero validator"); + require(!validatorStatus[validator], "ValidatorSet: duplicate validator"); + + validatorStatus[validator] = true; + validatorIndexes[validator] = validators.length; + validators.push(validator); + } + + /// @dev Removes a validator from the registered set while preserving the order of remaining validators. + /// Clears maintenance state first so `maintenanceCount` remains consistent. + function _removeValidator(address validator) private { + require(validatorStatus[validator], "ValidatorSet: validator not found"); + + if (maintenanceStatus[validator]) { + maintenanceStatus[validator] = false; + maintenanceCount--; + } + + uint256 index = validatorIndexes[validator]; + for (uint256 i = index; i < validators.length - 1; i++) { + address nextValidator = validators[i + 1]; + validators[i] = nextValidator; + validatorIndexes[nextValidator] = i; + } + + validators.pop(); + delete validatorIndexes[validator]; + delete validatorStatus[validator]; + } +} diff --git a/contracts/validatorcontract/validator.go b/contracts/validatorcontract/validator.go new file mode 100644 index 000000000..052a735ac --- /dev/null +++ b/contracts/validatorcontract/validator.go @@ -0,0 +1,51 @@ +// Copyright 2026 BOOSTRY Co., Ltd. +// This file is part of ibet-Core. +// +// ibet-Core is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ibet-Core is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with ibet-Core. If not, see . + +// Package validatorcontract is an on-chain validator set contract wrapper. +package validatorcontract + +//go:generate abigen --sol contract/validator_set.sol --pkg contract --out contract/validator_set.go + +import ( + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/contracts/validatorcontract/contract" +) + +// ValidatorSet is a Go wrapper around an on-chain validator set contract. +type ValidatorSet struct { + address common.Address + contract *contract.ValidatorSet +} + +// NewValidatorSet binds validator set contract and returns a wrapper instance. +func NewValidatorSet(contractAddr common.Address, backend bind.ContractBackend) (*ValidatorSet, error) { + c, err := contract.NewValidatorSet(contractAddr, backend) + if err != nil { + return nil, err + } + return &ValidatorSet{address: contractAddr, contract: c}, nil +} + +// ContractAddr returns the address of contract. +func (set *ValidatorSet) ContractAddr() common.Address { + return set.address +} + +// Contract returns the underlying contract instance. +func (set *ValidatorSet) Contract() *contract.ValidatorSet { + return set.contract +} diff --git a/contracts/validatorcontract/validator_test.go b/contracts/validatorcontract/validator_test.go new file mode 100644 index 000000000..5ee1e4e8c --- /dev/null +++ b/contracts/validatorcontract/validator_test.go @@ -0,0 +1,528 @@ +// Copyright 2026 BOOSTRY Co., Ltd. +// This file is part of ibet-Core. +// +// ibet-Core is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ibet-Core is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with ibet-Core. If not, see . + +package validatorcontract + +import ( + "context" + "crypto/ecdsa" + "math/big" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/contracts/validatorcontract/contract" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +const ( + operationAdd uint8 = 0 + operationRemove uint8 = 1 +) + +var validatorContractChainID = big.NewInt(1337) + +type testAccount struct { + key *ecdsa.PrivateKey + addr common.Address +} + +func newTestAccount(t *testing.T) testAccount { + t.Helper() + + key, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("failed to generate key: %v", err) + } + return testAccount{ + key: key, + addr: crypto.PubkeyToAddress(key.PublicKey), + } +} + +func newTestAuth(t *testing.T, account testAccount) *bind.TransactOpts { + t.Helper() + + auth, err := bind.NewKeyedTransactorWithChainID(account.key, validatorContractChainID) + if err != nil { + t.Fatalf("failed to create transactor: %v", err) + } + return auth +} + +func deployValidatorSet(t *testing.T) (*backends.SimulatedBackend, *contract.ValidatorSet, []testAccount) { + t.Helper() + + accounts := make([]testAccount, 6) + alloc := make(core.GenesisAlloc) + for i := range accounts { + accounts[i] = newTestAccount(t) + alloc[accounts[i].addr] = core.GenesisAccount{Balance: big.NewInt(1000000000000000000)} + } + + backend := backends.NewSimulatedBackend(alloc, 10000000) + initialValidators := []common.Address{ + accounts[0].addr, + accounts[1].addr, + accounts[2].addr, + accounts[3].addr, + } + _, _, validatorSet, err := contract.DeployValidatorSet(newTestAuth(t, accounts[0]), backend, initialValidators) + if err != nil { + backend.Close() + t.Fatalf("failed to deploy validator set: %v", err) + } + backend.Commit() + + return backend, validatorSet, accounts +} + +func deployValidatorSetWithValidators(t *testing.T, validators []common.Address) (*backends.SimulatedBackend, *types.Transaction, error) { + t.Helper() + + deployer := newTestAccount(t) + alloc := core.GenesisAlloc{deployer.addr: core.GenesisAccount{Balance: big.NewInt(1000000000000000000)}} + for _, validator := range validators { + alloc[validator] = core.GenesisAccount{Balance: big.NewInt(1000000000000000000)} + } + backend := backends.NewSimulatedBackend(alloc, 10000000) + + _, tx, _, err := contract.DeployValidatorSet(newTestAuth(t, deployer), backend, validators) + return backend, tx, err +} + +func mustTransact(t *testing.T, backend *backends.SimulatedBackend, transact func() (*types.Transaction, error)) { + t.Helper() + + tx, err := transact() + if err != nil { + t.Fatalf("transaction failed before mining: %v", err) + } + backend.Commit() + receipt, err := backend.TransactionReceipt(context.Background(), tx.Hash()) + if err != nil { + t.Fatalf("failed to fetch transaction receipt: %v", err) + } + if receipt.Status != types.ReceiptStatusSuccessful { + t.Fatalf("transaction reverted: %s", tx.Hash()) + } +} + +func mustReject(t *testing.T, backend *backends.SimulatedBackend, transact func() (*types.Transaction, error)) { + t.Helper() + + tx, err := transact() + if err != nil { + return + } + backend.Commit() + receipt, err := backend.TransactionReceipt(context.Background(), tx.Hash()) + if err != nil { + t.Fatalf("failed to fetch transaction receipt: %v", err) + } + if receipt.Status != types.ReceiptStatusFailed { + t.Fatalf("transaction unexpectedly succeeded: %s", tx.Hash()) + } +} + +func assertAddresses(t *testing.T, got []common.Address, want []common.Address) { + t.Helper() + + if !reflect.DeepEqual(got, want) { + t.Fatalf("validator mismatch\n got: %v\nwant: %v", got, want) + } +} + +func TestValidatorSetInitialState(t *testing.T) { + // Viewpoint: + // A freshly deployed 4-validator set exposes the same active/full validator list and derives f=1, quorum=3. + + backend, validatorSet, accounts := deployValidatorSet(t) + defer backend.Close() + + wantValidators := []common.Address{accounts[0].addr, accounts[1].addr, accounts[2].addr, accounts[3].addr} + activeValidators, err := validatorSet.GetValidators(nil) + if err != nil { + t.Fatalf("failed to get validators: %v", err) + } + assertAddresses(t, activeValidators, wantValidators) + + allValidators, err := validatorSet.GetAllValidators(nil) + if err != nil { + t.Fatalf("failed to get all validators: %v", err) + } + assertAddresses(t, allValidators, wantValidators) + + f, err := validatorSet.FaultTolerance(nil) + if err != nil { + t.Fatalf("failed to get fault tolerance: %v", err) + } + if f.Cmp(big.NewInt(1)) != 0 { + t.Fatalf("unexpected fault tolerance: %s", f) + } + + quorum, err := validatorSet.QuorumSize(nil) + if err != nil { + t.Fatalf("failed to get quorum size: %v", err) + } + if quorum.Cmp(big.NewInt(3)) != 0 { + t.Fatalf("unexpected quorum size: %s", quorum) + } +} + +func TestValidatorSetRejectsInvalidInitialValidators(t *testing.T) { + // Viewpoint: + // Constructor validation rejects empty, zero-address, and duplicate initial validator lists. + + for _, tt := range []struct { + name string + validators func() []common.Address + }{ + { + name: "empty", + validators: func() []common.Address { + return nil + }, + }, + { + name: "zero address", + validators: func() []common.Address { + return []common.Address{newTestAccount(t).addr, common.Address{}} + }, + }, + { + name: "duplicate", + validators: func() []common.Address { + account := newTestAccount(t) + return []common.Address{account.addr, account.addr} + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + backend, tx, err := deployValidatorSetWithValidators(t, tt.validators()) + defer backend.Close() + if err != nil { + return + } + backend.Commit() + receipt, err := backend.TransactionReceipt(context.Background(), tx.Hash()) + if err != nil { + t.Fatalf("failed to fetch deploy receipt: %v", err) + } + if receipt.Status != types.ReceiptStatusFailed { + t.Fatalf("invalid validator deployment unexpectedly succeeded: %s", tx.Hash()) + } + }) + } +} + +func TestValidatorSetAddAndRemoveByQuorum(t *testing.T) { + // Viewpoint: + // Add/Remove proposals do not execute before quorum and execute immediately when the third vote reaches 2f+1. + + backend, validatorSet, accounts := deployValidatorSet(t) + defer backend.Close() + + candidate := accounts[4].addr + // Two votes are recorded for the add proposal, but the candidate is not registered yet. + mustTransact(t, backend, func() (*types.Transaction, error) { + return validatorSet.VoteAddValidator(newTestAuth(t, accounts[0]), candidate) + }) + mustTransact(t, backend, func() (*types.Transaction, error) { + return validatorSet.VoteAddValidator(newTestAuth(t, accounts[1]), candidate) + }) + + isValidator, err := validatorSet.IsValidator(nil, candidate) + if err != nil { + t.Fatalf("failed to check candidate before quorum: %v", err) + } + if isValidator { + t.Fatal("candidate became validator before quorum") + } + votes, err := validatorSet.ProposalVotes(nil, operationAdd, candidate) + if err != nil { + t.Fatalf("failed to get add proposal votes: %v", err) + } + if votes.Cmp(big.NewInt(2)) != 0 { + t.Fatalf("unexpected add proposal votes: %s", votes) + } + + // The third vote reaches quorum and appends the candidate to the registered validator list. + mustTransact(t, backend, func() (*types.Transaction, error) { + return validatorSet.VoteAddValidator(newTestAuth(t, accounts[2]), candidate) + }) + isValidator, err = validatorSet.IsValidator(nil, candidate) + if err != nil { + t.Fatalf("failed to check candidate after quorum: %v", err) + } + if !isValidator { + t.Fatal("candidate did not become validator after quorum") + } + + allValidators, err := validatorSet.GetAllValidators(nil) + if err != nil { + t.Fatalf("failed to get all validators after add: %v", err) + } + assertAddresses(t, allValidators, []common.Address{accounts[0].addr, accounts[1].addr, accounts[2].addr, accounts[3].addr, candidate}) + + // Two votes are recorded for the remove proposal, but the candidate remains registered. + mustTransact(t, backend, func() (*types.Transaction, error) { + return validatorSet.VoteRemoveValidator(newTestAuth(t, accounts[0]), candidate) + }) + mustTransact(t, backend, func() (*types.Transaction, error) { + return validatorSet.VoteRemoveValidator(newTestAuth(t, accounts[1]), candidate) + }) + isValidator, err = validatorSet.IsValidator(nil, candidate) + if err != nil { + t.Fatalf("failed to check removal candidate before quorum: %v", err) + } + if !isValidator { + t.Fatal("candidate was removed before quorum") + } + + // The third remove vote reaches quorum and removes the candidate while preserving remaining validator order. + mustTransact(t, backend, func() (*types.Transaction, error) { + return validatorSet.VoteRemoveValidator(newTestAuth(t, accounts[2]), candidate) + }) + isValidator, err = validatorSet.IsValidator(nil, candidate) + if err != nil { + t.Fatalf("failed to check removal candidate after quorum: %v", err) + } + if isValidator { + t.Fatal("candidate was not removed after quorum") + } + + allValidators, err = validatorSet.GetAllValidators(nil) + if err != nil { + t.Fatalf("failed to get all validators after remove: %v", err) + } + assertAddresses(t, allValidators, []common.Address{accounts[0].addr, accounts[1].addr, accounts[2].addr, accounts[3].addr}) +} + +func TestValidatorSetCanReuseCandidateAcrossVersions(t *testing.T) { + // Viewpoint: + // The same candidate can be added, removed, added again, and removed again because proposal IDs are versioned. + + backend, validatorSet, accounts := deployValidatorSet(t) + defer backend.Close() + + candidate := accounts[4].addr + voteAdd := func(voters ...testAccount) { + for _, voter := range voters { + mustTransact(t, backend, func() (*types.Transaction, error) { + return validatorSet.VoteAddValidator(newTestAuth(t, voter), candidate) + }) + } + } + voteRemove := func(voters ...testAccount) { + for _, voter := range voters { + mustTransact(t, backend, func() (*types.Transaction, error) { + return validatorSet.VoteRemoveValidator(newTestAuth(t, voter), candidate) + }) + } + } + assertVersion := func(want int64) { + got, err := validatorSet.Version(nil) + if err != nil { + t.Fatalf("failed to get validator set version: %v", err) + } + if got.Cmp(big.NewInt(want)) != 0 { + t.Fatalf("unexpected validator set version: got %s, want %d", got, want) + } + } + + assertVersion(0) + + // First add executes at version 0 and advances the version. + voteAdd(accounts[0], accounts[1], accounts[2]) + isValidator, err := validatorSet.IsValidator(nil, candidate) + if err != nil { + t.Fatalf("failed to check candidate after first add: %v", err) + } + if !isValidator { + t.Fatal("candidate was not added") + } + assertVersion(1) + + // First remove executes at version 1 and advances the version. + voteRemove(accounts[0], accounts[1], accounts[2]) + isValidator, err = validatorSet.IsValidator(nil, candidate) + if err != nil { + t.Fatalf("failed to check candidate after first remove: %v", err) + } + if isValidator { + t.Fatal("candidate was not removed") + } + assertVersion(2) + + // The second add uses a fresh versioned proposal instead of the executed add proposal from version 0. + voteAdd(accounts[0], accounts[1], accounts[2]) + isValidator, err = validatorSet.IsValidator(nil, candidate) + if err != nil { + t.Fatalf("failed to check candidate after second add: %v", err) + } + if !isValidator { + t.Fatal("candidate was not re-added") + } + assertVersion(3) + + // The second remove also uses a fresh versioned proposal. + voteRemove(accounts[0], accounts[1], accounts[2]) + isValidator, err = validatorSet.IsValidator(nil, candidate) + if err != nil { + t.Fatalf("failed to check candidate after second remove: %v", err) + } + if isValidator { + t.Fatal("candidate was not removed again") + } + assertVersion(4) +} + +func TestValidatorSetRejectsInvalidVotes(t *testing.T) { + // Viewpoint: + // Governance votes must come from validators, target valid candidates, + // and be counted at most once per validator. + + backend, validatorSet, accounts := deployValidatorSet(t) + defer backend.Close() + + candidate := accounts[4].addr + // A zero-address add candidate and a non-validator voter are both invalid. + mustReject(t, backend, func() (*types.Transaction, error) { + return validatorSet.VoteAddValidator(newTestAuth(t, accounts[0]), common.Address{}) + }) + mustReject(t, backend, func() (*types.Transaction, error) { + return validatorSet.VoteAddValidator(newTestAuth(t, accounts[5]), candidate) + }) + + mustTransact(t, backend, func() (*types.Transaction, error) { + return validatorSet.VoteAddValidator(newTestAuth(t, accounts[0]), candidate) + }) + // The same validator cannot vote twice for the same operation/candidate proposal. + mustReject(t, backend, func() (*types.Transaction, error) { + return validatorSet.VoteAddValidator(newTestAuth(t, accounts[0]), candidate) + }) + + hasVoted, err := validatorSet.HasVoted(nil, operationAdd, candidate, accounts[0].addr) + if err != nil { + t.Fatalf("failed to check vote status: %v", err) + } + if !hasVoted { + t.Fatal("expected validator vote to be recorded") + } +} + +func TestValidatorSetMaintenance(t *testing.T) { + // Viewpoint: + // Maintenance excludes only the caller from active validators, + // enforces the f-validator cap, and restores the active set on exit. + + backend, validatorSet, accounts := deployValidatorSet(t) + defer backend.Close() + + // With 4 registered validators, f=1, so the first validator may enter maintenance. + mustTransact(t, backend, func() (*types.Transaction, error) { + return validatorSet.EnterMaintenance(newTestAuth(t, accounts[0])) + }) + + activeValidators, err := validatorSet.GetValidators(nil) + if err != nil { + t.Fatalf("failed to get active validators in maintenance: %v", err) + } + assertAddresses(t, activeValidators, []common.Address{accounts[1].addr, accounts[2].addr, accounts[3].addr}) + + maintenanceSize, err := validatorSet.MaintenanceSize(nil) + if err != nil { + t.Fatalf("failed to get maintenance size: %v", err) + } + if maintenanceSize.Cmp(big.NewInt(1)) != 0 { + t.Fatalf("unexpected maintenance size: %s", maintenanceSize) + } + + inMaintenance, err := validatorSet.IsInMaintenance(nil, accounts[0].addr) + if err != nil { + t.Fatalf("failed to get maintenance flag: %v", err) + } + if !inMaintenance { + t.Fatal("validator is not marked in maintenance") + } + + // A second simultaneous maintenance entry exceeds f, and non-validators cannot enter maintenance. + mustReject(t, backend, func() (*types.Transaction, error) { + return validatorSet.EnterMaintenance(newTestAuth(t, accounts[1])) + }) + mustReject(t, backend, func() (*types.Transaction, error) { + return validatorSet.EnterMaintenance(newTestAuth(t, accounts[5])) + }) + + // Exiting maintenance returns the validator to the active list in its original order. + mustTransact(t, backend, func() (*types.Transaction, error) { + return validatorSet.ExitMaintenance(newTestAuth(t, accounts[0])) + }) + activeValidators, err = validatorSet.GetValidators(nil) + if err != nil { + t.Fatalf("failed to get active validators after maintenance exit: %v", err) + } + assertAddresses(t, activeValidators, []common.Address{accounts[0].addr, accounts[1].addr, accounts[2].addr, accounts[3].addr}) +} + +func TestValidatorSetRemoveValidatorInMaintenance(t *testing.T) { + // Viewpoint: + // Removing a validator currently in maintenance clears its maintenance flag + // and decrements maintenance count. + + backend, validatorSet, accounts := deployValidatorSet(t) + defer backend.Close() + + // The validator remains eligible to vote while in maintenance, and three registered validators can remove it. + mustTransact(t, backend, func() (*types.Transaction, error) { + return validatorSet.EnterMaintenance(newTestAuth(t, accounts[0])) + }) + mustTransact(t, backend, func() (*types.Transaction, error) { + return validatorSet.VoteRemoveValidator(newTestAuth(t, accounts[0]), accounts[0].addr) + }) + mustTransact(t, backend, func() (*types.Transaction, error) { + return validatorSet.VoteRemoveValidator(newTestAuth(t, accounts[1]), accounts[0].addr) + }) + mustTransact(t, backend, func() (*types.Transaction, error) { + return validatorSet.VoteRemoveValidator(newTestAuth(t, accounts[2]), accounts[0].addr) + }) + + isValidator, err := validatorSet.IsValidator(nil, accounts[0].addr) + if err != nil { + t.Fatalf("failed to check removed validator: %v", err) + } + if isValidator { + t.Fatal("maintenance validator was not removed") + } + maintenanceSize, err := validatorSet.MaintenanceSize(nil) + if err != nil { + t.Fatalf("failed to get maintenance size after removal: %v", err) + } + if maintenanceSize.Sign() != 0 { + t.Fatalf("maintenance count did not decrement after removal: %s", maintenanceSize) + } + + activeValidators, err := validatorSet.GetValidators(nil) + if err != nil { + t.Fatalf("failed to get validators after maintenance removal: %v", err) + } + assertAddresses(t, activeValidators, []common.Address{accounts[1].addr, accounts[2].addr, accounts[3].addr}) +}