From f434a9bcc5028698118caae3f2a158d11c063694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Negovanovi=C4=87?= <93934272+Stefan-Ethernal@users.noreply.github.com> Date: Fri, 3 Feb 2023 17:14:19 +0100 Subject: [PATCH] PolyBFT consensus protocol (#899) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move PolyBFT to Edge (#774) * Move PolyBFT to edge Co-authored-by: Igor Crevar Co-authored-by: Sasa Prsic Co-authored-by: Stefan Negovanović Co-authored-by: Victor Castell * Build block with Txs in PolyBFT (#789) Co-authored-by: Stefan Negovanović Co-authored-by: Sasa Prsic * EVM-48: Add RootChain helper (#773) * EVM-57 PolyBft state transactions introduction (#809) * EVM-57 PolyBFT bridge enabled state txs Co-authored-by: Sasa Prsic * EVM-57 PolyBFT bridge enabled state txs Co-authored-by: Sasa Prsic * tidy. * pr fixes * pr fix 2 * small fix * TransactionType should be populated in receipt * nonce fix * write change original tx fix * createStateTransactionWithData change * pr fix more * revert ComputeHash for tx because id does not really matter Co-authored-by: Sasa Prsic * Migrate polybft e2e framework to edge (#797) Back-port the e2e framework + tests from v3 to edge as a separate set of tests. * v3 smart contracts integration (#796) * Initial changes * Init v3-contract submodule * Uptime changes and linter fixes * Use local build and test workflows (temporary) * Test workflow expand with init git submodules * Native transfer precompile * CommitTransaction in blockBuilder * adding workflow call secrets input (#799) * Add BLS aggregated signatures verification precompile * Change required gas for introduced precompiles * Add console precompile * Additional functions in bls package * Register validator command * Linter fixes, register subcommand for register validator command * Adapt genesis command * Cleanup * Change addresses of sidechain contracts so that they don't clash with precompile addresses * Marshal from address in transaction * Remove CommitTransaction and use WriteTx instead * Fix panic in register validator command * Get rid of panics in register validator * Increase gas limit for ChildValidatorSet initialization * Cleanup GenesisPostHookFactory * Fix register validator command * Change gas limit to 100M for initialize child validator set * Change make compile v3-contracts * Recover CI (1st attempt) * Recover CI (2nd attempt) * Recover CI (3rd attempt) * Cleanup Makefile (remove compilation of SCs when unneeded). Remove cloning submodules in lint workflow * Recover lint * Remove init submodules from test workflow * Address some go security issues * small make fix * init root contracts command * Remove rootchain contracts deployment from rootchain server command * Provide new url for smart contracts * Introduce constants for register validator and contracts root folder * Comments * Comments (part 2) * Reference dev-ethernal commit from core-contracts * Fix polybft e2e workflow * Fix genesis cmd invocation from e2e framework * contract_initializer fix * Fix polybft e2e tests * Comments (part 3) * Populate validators balance based on provided genesis configuration. Comments (part 4) * Fix balance unmarshal for polybft.Validator * Initialize native transfer contract Co-authored-by: Goran Rojovic Co-authored-by: Epikichi <78609649+epikichi@users.noreply.github.com> Co-authored-by: Igor Crevar * EVM-129 Implement bridge workflow (#828) * Add bridge flag. * Populate bridge config. * Fix bridge address. * rootchain panic fixes * changes. deploying contracts to root still does not work in test * fix deploying contracts and running bridge. Emit command change * Read balance from genesis * Remove Geth reference from Edge module * bridge test fixes * remove test-bootnode. add WithBootnodeCount to test-cluster. Change network_test to be more relaxed Co-authored-by: Sasa Prsic Co-authored-by: Igor Crevar * Transfer unit tests from v3 to Edge (#825) * test-bridge fix + temporary fix * Read balance from genesis * state, state transaction, event tracker * FSM tests (part 1) * ConsensusRuntime tests * FSM tests (part 2) * FSM tests (part 3) * SystemState tests * ValidatorAccount and ValidatorSnapshot tests * buildLogsFromReceipts test * FSM tests (part 4) * FSM fix remaining tests * t.parallel * Introduce parallel execution in tests (part 2) * Lint fixes * Rename test helpers file to exclude it from build * Add test suffix to mocks * Install solidity compiler on test workflow * Assign execute permission to setup-ci.sh script * polybft tests * all tests merged * setting epoch snapshot added * Lint fixes * Use constant for IstanbulExtraVanity * Description of polybft test Co-authored-by: Igor Crevar Co-authored-by: Nemanja0x Co-authored-by: Goran Rojovic * [Checkpointing] `ExitEvent` storage and proof generation (#838) * Exit event storage and query functions * ExitProof generation * Reorganize tests * Optimize key composition * Extract common function for decoding state sync and exit events * Comments fix * Comments fix * [Checkpointing] JSON RPC point for generating exit proofs (#844) * JSON RPC point for generating exit proofs * Comments fix Co-authored-by: Stefan Negovanović * Checkpoint data signing (#840) * Add CheckpointData to Extra * Don't store chainID, blockNumber and blockHash to the blockchain * Set checkpoint data to the Header.Extra * Fix and simplify FSM unit tests * getSignHash static function, fix verify header implementation * Increase block waiting time in bridge test * Remove unnecessary condition * Set checkpoint to each extra * Sign checkpoint hash instead of aggregated hash * Make nextValidatorsHash same as currentValidatorsHash for non-epoch ending blocks * Set event root to the checkpoint object * [Checkpoints] Parse receipt logs and insert new exit events (#852) * Parse receipt logs and insert exit events * Comments fix * Comments fix * Comments fix * Remove TODO * Comments fix * Comments fix * Comments fix * Consolidate BLS verification ABI type (#867) * Checkpoint submission (#861) * Change error messages for checkpoint hash calculation * Determine checkpoint blocks * Checkpoint submission (draft) * Introduce checkpoint manager * Lint fixes * Fix tests * Checkpoint manager checkpoint data ABI encode unit tests * Unit test for getCurrentCheckpointID * Unit test for submitCheckpoint * Remove leftover code * Comments * Determine epoch ending blocks by comparing epoch numbers in adjacent headers * Micro optimization * Skip querying first block for end of epoch * Optimization * Simplify checkpoints offset * Adapt comment * Checkpoint block detection optimization (#872) * Simplify checkpoint block detection * Resolve conflicts and pass tests Co-authored-by: Boris * Simplify * Submit pending checkpoints in unit test * Extract parentHeader retrieval outside of the for * Submit end of epoch blocks for pending checkpoints * Update submitCheckpoint comment Co-authored-by: Victor Castell * Feedback * Separate import declarations * Create var block for `parentExtra` and `parentHeader` Co-authored-by: Victor Castell Co-authored-by: Goran Rojovic Co-authored-by: Boris Co-authored-by: Victor Castell * Mock Host implementation in native transfer test (#887) * Mock Host implementation in native transfer test * Lint fix * Fix polybft e2e tests * Implement the logic for quorum check (#837) * Implement the logic for quorum check Depending on the message type it will implement different kinds of quorum checks. * Remove MaximumFaulty * Integrate go-ibft consensus engine to polybft consensus protocol (#891) * EVM 149 - syncer go ibft coordination (#843) * consensus runtime backend metohod (not buildable) * transport * polybft - run cycle * transport fix 2 * small changes * less complex ibft consennsus wrapper.Example: https://replit.com/@crewce/Newest#main.go * small changes * runSequence better * remove pbftTransportWrapper * noone likes temp variables * Comment fixed * jm fixes Co-authored-by: Igor Crevar * EVM-151 Use (or not) edge hooks support (#849) * FSM that supports go-ibft backend interface (#865) * consensus runtime backend metohod (not buildable) * transport * polybft - run cycle * transport fix 2 * less complex ibft consennsus wrapper.Example: https://replit.com/@crewce/Newest#main.go * Implement MessageConstructor interface. * Tidy linter errors. * runSequence better * remove pbftTransportWrapper * Add quorum size implementation. * Remove one part of pbft.NodeID reference. * Add some comments. * IsValid, Build proposal on FSM * Add block validation. * Add stub for IsValidSender method. * Sender validation is always true. * fsm - Insert block * simplify build proposal * Tidy logs. * IsValidSender * pbft.Proposal removed * Tidy errors. * restartEpoch if epoch is nil, remove hook in fsm * initialization fix * Fix compare. * quorum fixes and error propagation * evm-151 changes + additional needed in this task * Increase test timeout. * bring back some part of fsm * just execute syn test 100 times in a row * Tidy part of errors. * Revert e2e test. * Remove unused code and move message creator interface implementation. * minor fix * Add comments. * code organization - valdateSender + recoverAddress * Fix compile after merge. * small change * restart epoch on better place. lastBuiltBlock and epoch should not be nil * small reorg in polybft * pr fix * Update comments * pr fixes no 2 * Add voting power to the validators (#880) * Add voting power to the validator account * Rename ValidatorAccount to ValidatorMetadata * Set voting power to the existing test * Comment fixes * Rename files * Address comments * Remove extra space from the comment * pr fixes * Rename GetValidatorAccount to GetValidatorMetadata Co-authored-by: Nemanja0x Co-authored-by: Igor Crevar Co-authored-by: Stefan Negovanović Co-authored-by: Stefan Negovanović <93934272+Stefan-Ethernal@users.noreply.github.com> * Accommodate go-ibft `BuildProposal` updated function (#893) * Populate round info and change proposal hash * Adopt currentRound from BuildProposal * Fix logs * Fix proposal hash calculation inside IsValidProposalHash * Update go-ibft version * Igor's feedback addressed * Merge fix * EVM-187 Handling bundles on both sync and consensus (#882) * EVM-187 Handling bundles on both sync and consensus * consensus_runtime tests * UT fix Co-authored-by: Goran Rojovic * Implement HasQuorum function in consensusRuntime Co-authored-by: Nemanja Nedic <93836814+Nemanja0x@users.noreply.github.com> Co-authored-by: Igor Crevar Co-authored-by: Saša Pršić <93726535+0xSasaPrsic@users.noreply.github.com> Co-authored-by: Nemanja0x Co-authored-by: Goran Rojovic * Remove Polybft dummy smart contracts (#909) * Change ValidatorMetadata methods to pointer receivers (#911) * EVM-152 Fix Unit Tests (#910) Co-authored-by: Goran Rojovic * adding devnet-v3 workflow for parity testing. Adding needed secrets for other workflows (#916) * Introduce local development cluster script (#912) * Introduce local development cluster script * Fix assigned boot nodes port number in genesis (#930) * Local cluster - adapt port for ibft to polybft (#931) * Fix unit test for event tracker (#935) * Fix linters * Fix TestEventTracker_TrackSyncEvents * Comment * EVM-232 Keep polybft bls and ecdsa keys in separate secrets manager namespaces (#953) * Track updates for validators (#939) * Implement equality checks for PublicKey and ValidatorMetadata. Expand ValidatorSetDelta * Handle updates in ApplyDelta * Additional tests * Epikichi/edge 973 change v3 parity workflow to devnet (#957) * reflecting changes to use ephemeral environment instead * Epikichi/edge 964 implement multiple erc mode support (#954) * adding support for ERC20 and ERC721 mode for Pandora's Box stress testing * adding in default for devnet ephemeral anticipation * fixing markdown format * reverting for a separate PR * adding changes for devnet ephemeral workflow to allow dispatching * adding correct job names, removed dependencies since concurrency protection is set on Pandora' workflow env-wide * reflecting chnages from develop * EVM-220 TestClusterBlockSync/BLS fails in voting power branch (#926) * Fix data race to polybft unit test (#965) * Fix some race conditions. * Polybft voting power (#944) voting power feature * Align `v3-parity` with `develop` branch (#971) * Epikichi/edge 964 implement multiple erc mode support (#954) * adding support for ERC20 and ERC721 mode for Pandora's Box stress testing * adding in default for devnet ephemeral anticipation * fixing markdown format * reverting for a separate PR * introducing workflow dispatch trigger for Devnet V3 workflow. (#927) * introducing workflow dispatch trigger for Devnet V3 workflow. * adding ephemeral environment naming * adding pandora modes * Add performance tests to devnet deployments (#918) * added performance tests to devnet deployment * removing depricated loadbot performance tests * Docker setup: Wait for genesis file to exist before starting nodes (#949) * Docker setup: Wait for genesis file to exist before starting nodes * Docker setup: don't run init if genesis file already exists * adding conditional dependency for compose to wait for successful node Co-authored-by: epikichi Co-authored-by: Epikichi <78609649+epikichi@users.noreply.github.com> Co-authored-by: ZeljkoBenovic <47507986+ZeljkoBenovic@users.noreply.github.com> Co-authored-by: mediremi-antithesis <107197847+mediremi-antithesis@users.noreply.github.com> Co-authored-by: epikichi * Fix e2e tests * Set address to the result instance on applyCreate * Remove State Sync execution step (#964) * Remove commitment execution related stuff * Add the RPC endpoint to obtain StateSync Proofs * Rootchain data to gitignore * Return the state sync along with proof * Better params for cluster script * E2E test executing state syncs * Fix tests that rely on bundle execution * Remove commitment execution related stuff * Replace BLS library with github.com/kilic/bn254 (#978) * Integrate with CheckpointManager.sol (#952) * Integrate with CheckpointManager.sol * Hex encode ECDSA private key * Skip failing tests for checkpoint manager * Fix submit function signature * Remove logs and set From field to submit checkpoint transaction * Pass signer key to rootchain.SendTxn function * Pull console precompile Geth docker image * Populate voting power on checkpoint manager initialization * Hash change * Minor fixes (ordering of abi ancoded parameters) * Populate voting power on genesis block * Fix balance scaling * Fix TestCheckpointManager_isCheckpointBlock * Remove sorting by addresses on validators snapshot calculation * Sort validators by addresses on genesis header population * Sort validators by addresses on rootchain initialization * Logging... * Update core-contracts * Fix checkpoint manager unit tests * Change comment + unit tests for helper functions * Revert usage of Geth console fork * Include Geth console fork again * Rename latestCheckpointBlockNumber to currentCheckpointBlockNumber * Remove logs and revert to use official Geth image for rootchain server * Code cleanup * Add meaningful logs to the checkpoint manager * not buildable * full functional bn254 * Use proper domain when initializing CheckpointManager contract * Fix go mod * Revert helper bash scripts * Fix e2e tests * Address Igor's comment * Provide `epochSize` to the `ChildValidatorSet` (#983) * Provide epochSize to the ChildValidatorSet * Rename newBls map key * Rename parameters * Point to our branch in core-contracts Co-authored-by: Goran Rojovic Co-authored-by: Stefan Negovanović Co-authored-by: Nemanja0x Co-authored-by: Igor Crevar Co-authored-by: Stefan Negovanović <93934272+Stefan-Ethernal@users.noreply.github.com> * Change genesis premine logic (#981) * Fix data race in fsm (#982) * Fix data race in several places accessing fsm and fix copy validators function. * tidy linter. * Fix tests after changes and user RLock. * Removing `GenesisTarget` and adding the `NodeID` field to `Validator` struct (#985) * Refactored GenesisTarget removing it and adding the NodeID field to Validator struct. * Refactored generatePolyBFTConfig to use the new struct and simplify the code and take into account validators passed from params * Add NodeID as a field needed for validators passed from params * Tune timeout for bridge test Co-authored-by: Stefan Negovanović * Provide rootchain IP address from CLI (#989) * Refactor RootchainInteractor abstraction * Lint fix * Provide JSON RPC address flags * Remove lock from rootchain interactor. Send transaction remove nonce parameter. * Address comments * Reduce amount of affected files * Implement TxRelayer * Address comments (part 2) * Create txn with nonce set * Option pattern in TxRelayer * Use txRelayer in e2e tests * Let TxRelayer take care of setting the nonce * Fix tx pool e2e tests * Added london hardfork to the chain config (#999) * Fix pending checkpoints submission (#992) * Fix epoch ending blocks detection in pending checkpoints * Update TestCheckpointManager_submitCheckpoint * Remove redundant signerAddress * Make unit test more understandable * Update core-contracts (#1007) * Bundle build fix (#1014) * Check for nil proposal (#1009) * Remove gasLimit and gas fields from txrelayer call (#1020) * Add default gas and gasPrice to local transactions (#1021) * Use geth console fork as default with flag to disable (#1022) * Checkpoint submission e2e test (#990) * Checkpoint submission e2e test * Utilize TxRelayer in test * Fix init contracts command * Fix e2e test * git modules: shallow and fix branch (#987) * shallow and fix branch * feat-polybft-release * Remove bool return value from deliverMessage signature (#1027) * Fix/immutable trie race (#1017) * Add lock to trie state. * Use total stake for voting power (#1038) * Use total stake as voting power * Lower log level to debug log in local cluster script * Fix unit test * RLP Marshal/Unmarshal update field in `ValidatorSetDelta` (#1026) * RLP Marshal/Unmarshal update field in ValidatorSetDelta * Comment + additional unit test * Remove unused fields (#1039) * Introduce metrics for BoltDB for the state (#1045) * Expose `PolyBFT` metrics to Prometheus API (#1040) * Introduce metrics to polybft * Add comment * Update comment * Remove total state syncs metrics * Include metrics prefix and rename file * Address comment * Fix consensus runtime tests * EVM-308 Add commands we are missing (#1050) * Added new commands * Merge params and result files * Reorganize commands * Comments fix * Fix loop closure violations in unit tests (#1053) * Fix loop closures * Make TestExtra_CreateValidatorSetDelta_Cases deterministic * Avoid for loop when assigning voting power * Minimize changes * Fix * Integrate L2 exit workflow (#1024) * e2e exit test * fix lint * add static L1 exit contract data * add contract comilation to e2e tests * fix ci * save artifacts in client * fix tests * review fix * review fix * Update consensus/polybft/contractsapi/gen/main.go Co-authored-by: Stefan Negovanović <93934272+Stefan-Ethernal@users.noreply.github.com> * review fix Co-authored-by: Stefan Negovanović <93934272+Stefan-Ethernal@users.noreply.github.com> * Add pending checkpoints assertions to checkpoint submission e2e test (#1052) * Decouple rootchain contracts deployment from development environment (#1005) * Rootchain init contracts read validators from the genesis configuration. Introduce manifest.json. * Introduce admin-key flag to the rootchain init-contracts * Revert polybft params changes * Fix comment * Populate rootchain admin address to manifest * Update Manifest struct and adapt init-contracts workflow * Remove sidechain smart contract addresses from genesis spec * Update genesis command and cleanup genesis from unnecessary parameters * Remove rootchain contracts hardcoded addresses * Decouple emit command from contant StateSender address * Manifest command * Polybft manifest command * Adapt e2e tests workflow * Update comment * Adapt cluster script and change flags descriptions in manifest command * Comments * Adapt TestE2E_CheckpointSubmission * Remove manifest.json in cluster script * Initial version of polybft readme * Update readme with a precondition step * Address comments (part 1) * Update manifest command short help * Comments * Use sidechain addresses from the polybft config in fsm UTs * Minimize changes * Merge fix Co-authored-by: Goran Rojovic * EVM-302 Save proposer priorities after each block (#1018) * EVM-302 Save proposer priorities after each block Co-authored-by: Nemanja0x Co-authored-by: Stefan Negovanović Co-authored-by: Sasa Prsic * Remove GossipMessage (#1057) * Consolidate `ExtendByteSlice` implementations (#1058) * Consolidate ExtendByteSlice implementations * Add comment and fix * Introduce trim parameter (fix unit tests) * Update bitmap unit tests * Address comment * Add a unit test * Remove readContractBytecode (#1061) * Evm 303: Fix race conditions after a block is inserted (#1047) Co-authored-by: Igor Crevar Co-authored-by: Stefan Negovanović Co-authored-by: Sasa Prsic * Feature/validator set changes (#1048) * Update priorities when validator set changes. * Remove code related to bundles for state syncs (#1013) Related to RFC-139, we're removing bundles concept from the codebase. We're introducing the latest Smart Contracts version in this PR * Create a full block object during polybft (#1063) * Create a full block object during polybft * Remove print * Remove now * Fix tests * Fix segmentation fault panic on `ValidateSender` (#1054) * Add nil check for FSM * Different approach: consensus runtime validates message sender. Remove Includes function from validatorSet. * Subscribe IBFT topic only after epoch is initialized * Renames and comment fixes * Revert sender validation to the fsm * Rebase fix * Add log * RFC173 Checkpoint consensus issue (#1064) * RFC 173 changes * Comments fix * Comments fix * Fix uptime total blocks calculation (#1069) * Fix uptime total blocks calculation * Update test for uptime calculation * Simplify * Simplify unit test * Typo fix * Implement the relayer (#1044) * Implement the relayer * The statesyncrelayer package will reside inside polybft * Immutable trie simplification (#1068) * Refactor immutable trie. * Remove state from trie. * Tidy. * Remove storage from trie. * Remove voting power scaling (#1060) * Change voting power from uint64 to big.Int * Provide desired scaling factor to validator set * Fix unit tests after rebase * Fix validatorSetToABISlice comment * Introduce relative voting power * Change ProposerPriority to big.Int * Fix log * Fix computeMaxMinPriorityDiff * Remove max total voting power hard limit * Use truncated division for rescaling priorities * Add ceil to quorum size calculation * Revert unnecessary changes * Remove safe_math * Compress quorum calculation Co-authored-by: Victor Castell * Extract helper function for big int with ceil division Co-authored-by: Victor Castell * Epikichi/edge 988 add polybft consensus support to edge (#1051) * Added support for polybft consensus docker cluster * added customisation options * added/edited readme Co-authored-by: ZeljkoBenovic Co-authored-by: ZeljkoBenovic <47507986+ZeljkoBenovic@users.noreply.github.com> * Implement `TestE2E_Bridge_ChangeVotingPower` (#1075) * Implement TestE2E_Bridge_ChangeVotingPower * Increase epoch rewards * Add log to the fsm.getCurrentValidators * Move default epoch reward to genesis cmd * Dump validators to log only when debug logs enabled * Multiple validators are staking * Remove unnecessary panics from polybft (#1077) * Register validator remove panics and expose json rpc flag * Remove panic from blockchainWrapper * Remove json rpc flag from register validator cmd * Polybft log instead of panic if block synchronization fails * Remove unused vars * Handle createValidatorSetDelta error * Rename test * Revert panic in stateProvider * Dynamic epoch size (#897) * Decouple uptime calculation from fixed epoch size * Tests fix * Rename isEndOfEpoch * Return error on apply delta to validator set * Comments fix * Comments fix * Comments fix * Comments fix * Comments fix * Bug fix * Comments fix * Rebase fix * Rebase fix * Rebase fix * Rebase fix * Modify snapshot calculation for dynamic epoch (#997) * Modify snapshot calculation for dynamic epoch * Comments fix * Comments fix * Move tests to new file * Comments fix * Rebase fix * Comments fix * Comments fix * Feature - State Sync manager (#1067) * initial work * Rework on event tracker and tests * More stuff * Migrate last test * More changes * Remove unused test * Use correct core-contracts version * Remove logs * Use eventTracker from tracker package, code cleanup, bug fixes * Code cleanup * Code cleanup * Comments fix * Dummy state sync manager when bridge is not enabled * Comments fix * Comments fix * Rebase fix Co-authored-by: Goran Rojovic * Core contracts to dev-audit (#1095) * Core contracts to dev-audit * Fix event name * Implement `Stringer` interface in `AccountSet` (#1103) * Implement stringer in AccountSet * Use strings.Join instead of defining buffer explicitly * Add disclaimer message when running polybft consensus protocol (#1107) * Add disclaimer message when running polybft consensus protocol * Print disclaimer message to logger and CLI outputter * Register validator e2e test (#1084) * Initial approach * Rely on system state in TestE2E_Consensus_RegisterValidator * Add stake and balance flags to register validator cmd * Remove debug log * Add appropriate assertions * Revert scientific notation changes * Add comments * Remove grpc flag from register validator cmd * Delegation and undelegation e2e test (#1086) * Initial approach * Delegation logic * Minor changes to unstake and withdraw commands * Withdrawal and undelegate in test * Change unstake cmd description * Query for current block number before outputting delegator info * Handled go routines in event tracker (#1080) * Manage goroutines in EventTracker * Stop StateSyncRelayer * Remove closeCh from polybft and use context instead * Create named logger in the event tracker itself * Update consensus/polybft/polybft_test.go assertion message Co-authored-by: Victor Castell * Remove context field from consensusRuntime * Isolate changes (revert changes in polybft to rely on channel) * Add function comments * Fix test Co-authored-by: Victor Castell * Multiple state sync commitments per epoch (#1083) * Initial changes * e2e test * Code cleanup * Comments fix * Lock fix * Comments fix * Comments fix * Comments fix * Comments fix * Update comment in state_sync_manager Co-authored-by: Stefan Negovanović <93934272+Stefan-Ethernal@users.noreply.github.com> * Return FullBlock from syncer (#1110) * Return FullBlock from syncer * Comments fix * Comments fix * Extract common parts of WriteBlock and WriteFullBlock * Revert "Extract common parts of WriteBlock and WriteFullBlock" This reverts commit 62166e6877f5a6a6f4a780d7047511caa530b8df. Co-authored-by: Stefan Negovanović * EVM-356 Update checkpoint out of fsm (#1113) * Move logic from fsm to checkpoint manager * PostBlock and PostEpoch in checkpointManager * Function comment fix * Call PostEpoch on checkpointManager * Test fix * e2e tests fix * Comments fix * Comments fix * Change test * Comments fix * Move BuildEventRoot to checkpointManager (#1115) * Initialize `closeCh` in `stateSyncManager` (#1116) * Initialize closeCh in stateSyncManager * Rename test * Add test for state sync relayer * Move GenerateExitProof to checkpointManager (#1118) * Invert logical condition in `WaitUntil` functions (#1123) * Invert logic for bridge WaitUntil function * Invert WaitUntil logic in TestCluster * Change condition in TestE2E_Bridge_ChangeVotingPower * Fix flaky register validator e2e test (#1122) * Invert logic for bridge WaitUntil function * Invert WaitUntil logic in TestCluster * Change condition in TestE2E_Bridge_ChangeVotingPower * Fix flaky test * Query validators multiple times * TestE2E_Bridge_L2toL1Exit to work with multiple users (#1132) TestE2E_Bridge_L2toL1Exit to work with multiple users * Use PostBlock handler in ProposerCalculator (#1139) * Add missing cluster stop invocations in e2e tests (#1138) * Optimized docker build in docker-compose (#1135) * Validator unstake e2e test (#1112) * Initial approach * Tweak test * Invert WaitUntil condition * Premine balance to child smart contracts * Working withdrawal * Remove redundant inline function * Pre-allocate ChildValidatorSet in genesis with initial validator set total stake * Rebase fix * Address comments * E2E Relayer (#1131) E2E Tests for Relayer We need to check if StateSyncs are being executed correctly by one of the nodes of the cluster, so we are removing the "manual" execution previously present in e2e test and relay on the Relayer feature to do it as normal operation. * BuildProofs for missed commitments (#1141) * BuildProofs for missed commitments * Comments fix * Comments fix * EVM-360 Invalid state root error on v0.7.1-alpha2 (#1133) Change block builder Reset to include timestamp for current block and call Reset each time before proposal is created and not just once when we are creating fsm * Rely on contracts api package when dealing with ABI abstractions (#1144) * Add remaining smart contract artifacts * Use ABI functions, events and types from Artifact abstractions * Unexport stateSyncABIType variable * Extract StateSynced event from correct contract * Rename parameters so that they match smart contract spec * Fix unstake and undelegate * Typo fix * Fix undelegate e2e test * Align RLP marshaling logic according to eip-2718 (#1114) * EVM-360 e2e test (#1158) * EVM-353 Different voting power property based test (#1149) * Remove GetNonce function (#1165) * Expose ValidatorInfo struct (#1162) * Feature contractsapi gen (#1062) * Add initial gen api * Remove method suffix * Add contracts to the main function * Add struct encoding * Small fix * Generate contract namespace (placeholder) * Handle leading underscore in field name * Comments fix * Comments fix * Take events from artifact * Event name fix * Remove contracts stubs from generated code * Remove unnecessary code * Real type names * Comments fix * Comments fix Co-authored-by: Goran Rojovic * TestCommitEpoch unit test (#1167) TestCommitEpoch * EVM-395 Implement RFC-175 BLS integration (#1161) * EVM-395 Implement RFC-175 * Add hash to point unit tests * Avoid random secret keys with zero value * Check point at infinity and subgroup Co-authored-by: Ferran Borreguero * Removed legacy logic from tx pool (#1171) * Introduce test smart contracts as artifacts (#1163) * Introduce L1DummyStateReceiver as artifact * Extract TestL1StateReceiver smart contract to separate file and embed it * Include TestWriteBlockMetadata artifact * Introduce constant for test-contracts dir * Checkpoint data validations (#1160) * Validate checkpoint data * Separate Validate and ValidateBasic checkpoint data validations * Summarize logical conditions * Vefify state transactions before writing commit epoch transaction * Prevent double execution of commit epoch transaction by providing callback to ProcessBlock * Simplify * EVM-378 Use contracts api generated stubs in client code (#1166) * Use contracts api for commitments * Move ExitProof and StateSyncProof in another package * Use state synced event from generated code * Use generated structs for comit epoch (uptime) * Rebase fix * Use contracts api in checkpoints * Use contracts api in bridge test * Remove unnecessary logs * Rebase fix * Comments fix * Comments fix * Rebase fix * Remove unused constants * Comments fix * Comments fix * Rename functions and add comments --------- Co-authored-by: Stefan Negovanović * Fix EVM-372 & EVM-373 already existing directories and files are not checked for ownership and permissions (#1164) * Implemented helper create file and dir functions * Replace writeFile with CreateFileSafe * replace mkdirall with CreateDirSafe * change config file error message * Add extra empty path guard * Fix not overwriting files issue * remove shouldNotExist parameter * remove chmod update * fix lint * rename to save file * comment fix * comment fix * one-liner return and handle errors * update error message * Adapt go-ibft implementation for polybft (#1185) * Fix hash calculation for proposal for go-ibft's update (#1065) * Changing the signatures of the backend methods. * Fixed all method signatures that expect a proposal. * Verify committed seals created by ethereum block and round number * Remove unnecessary fields before hash calculation for CS * Fix missing field in packProposerSealIntoExtra * Fix hash calculation for the block including round number * Refactor hash calculation with round * Fix build error test * Revert "Refactor hash calculation with round" This reverts commit b527ecb08e3d786ebf59d7988c86b934425b8582. * Fix failed test * Fix HasQuorum * Rename method in IBFT Backend * Fix method comment * Rename IsValidBlock to IsValidProposal in IBFTBackend * Fix parsing of RoundNumber in IBFTExtra * Fix packCommittedSealsAndRoundNumberIntoExtra * Revert packCommittedSealsAndRoundNumberIntoExtra * Fix hash calculation for committed seals and revert interface of block verifier * Update go-ibft * Fix lint error * Fix marshaling of IBFT Extra * Fix minor issues * Fix comment --------- Co-authored-by: Lazar Travica * Integrate updated go-ibft changes * Error handling in BuildProposal * Fix logs * Add view instance to BuildProposal * Update go-ibft version * Formatting --------- Co-authored-by: kourin Co-authored-by: Lazar Travica * [EVM-370] Added --insecure flag to both ibft and polybft (#1182) * Added --insecure flag to both `ibft` and `polybft` * Provide insecure flag in `polybft` e2e framework * Provide insecure flag to `ibft` e2e framework --------- Co-authored-by: Stefan Negovanović * Reject contract creation transactions with large init data (#1181) * Fix validation of state transactions (#1186) * Fix validation of state transactions * Add a comment * Comments fix * Add unit test * Return error if two uptime transactions are in a block --------- Co-authored-by: Stefan Negovanović * EVM-416 Use one struct for Exit and StateSync proofs in jsonrpc (#1170) * Use one struct * Fix state sync execution * Remove abi functions from StateSyncProof * Comments fix * Comment --------- Co-authored-by: Nemanja Nedic <93836814+Nemanja0x@users.noreply.github.com> Co-authored-by: Igor Crevar Co-authored-by: Sasa Prsic Co-authored-by: Victor Castell Co-authored-by: Roman Behma <13855864+begmaroman@users.noreply.github.com> Co-authored-by: Victor Castell Co-authored-by: Goran Rojovic Co-authored-by: Epikichi <78609649+epikichi@users.noreply.github.com> Co-authored-by: Nemanja0x Co-authored-by: Goran Rojovic <100121253+goran-ethernal@users.noreply.github.com> Co-authored-by: Boris Co-authored-by: Saša Pršić <93726535+0xSasaPrsic@users.noreply.github.com> Co-authored-by: ZeljkoBenovic <47507986+ZeljkoBenovic@users.noreply.github.com> Co-authored-by: mediremi-antithesis <107197847+mediremi-antithesis@users.noreply.github.com> Co-authored-by: epikichi Co-authored-by: Ferran Borreguero Co-authored-by: Evgeny Danilenko <6655321@bk.ru> Co-authored-by: b00ris Co-authored-by: ZeljkoBenovic Co-authored-by: Ahmet Yazıcı <75089142+yaziciahmet@users.noreply.github.com> Co-authored-by: kourin Co-authored-by: Lazar Travica --- .github/workflows/build.yml | 7 + .github/workflows/ci.yml | 3 + .github/workflows/deploy.devnet.yml | 3 + .github/workflows/deploy.testnet.yml | 3 + .github/workflows/deploy_edgenet.yaml | 60 +- .github/workflows/e2e-polybft.yml | 36 + .github/workflows/e2e.yaml | 5 +- .github/workflows/test.yml | 6 + .gitignore | 4 + .gitmodules | 8 +- .golangci.yml | 5 + Makefile | 28 +- archive/restore.go | 4 +- archive/restore_test.go | 4 +- blockchain/blockchain.go | 121 +- blockchain/blockchain_test.go | 15 +- blockchain/storage/testing.go | 18 +- chain/chain.go | 10 + chain/chain_test.go | 47 + chain/params.go | 8 + command/cli_output.go | 36 +- command/e2e/params.go | 51 + command/e2e/register_validator.go | 504 ++ command/genesis/genesis.go | 71 +- command/genesis/params.go | 48 +- command/genesis/polybft_params.go | 257 + command/genesis/utils.go | 141 +- command/helper/helper.go | 3 +- command/json_output.go | 33 +- command/output.go | 22 +- command/polybft/polybft_command.go | 27 + command/polybftmanifest/manifest_init.go | 210 + command/polybftsecrets/params.go | 242 + command/polybftsecrets/result.go | 68 + command/polybftsecrets/secrets_init.go | 41 + command/results.go | 16 + command/root/root.go | 11 +- command/rootchain/README.md | 30 + command/rootchain/emit/emit.go | 187 + command/rootchain/emit/params.go | 51 + command/rootchain/emit/result.go | 30 + command/rootchain/fund/fund.go | 143 + command/rootchain/fund/params.go | 87 + command/rootchain/fund/result.go | 28 + command/rootchain/helper/metadata.go | 93 + .../rootchain/initcontracts/init_contracts.go | 351 ++ command/rootchain/initcontracts/params.go | 35 + command/rootchain/initcontracts/result.go | 53 + command/rootchain/rootchain.go | 38 + command/rootchain/server/params.go | 11 + command/rootchain/server/result.go | 41 + command/rootchain/server/server.go | 298 + command/secrets/init/result.go | 14 - command/secrets/init/secrets_init.go | 27 +- command/server/config/config.go | 2 + command/server/export/export.go | 8 +- command/server/init.go | 2 + command/server/params.go | 4 + command/server/server.go | 7 + command/sidechain/helper.go | 111 + command/sidechain/staking/params.go | 58 + command/sidechain/staking/stake.go | 166 + command/sidechain/unstaking/params.go | 58 + command/sidechain/unstaking/unstake.go | 164 + command/sidechain/validators/params.go | 46 + .../sidechain/validators/validator_info.go | 77 + command/sidechain/withdraw/params.go | 45 + command/sidechain/withdraw/withdraw.go | 126 + consensus/consensus.go | 14 +- consensus/dev/dev.go | 6 +- consensus/dummy/dummy.go | 4 + consensus/ibft/consensus_backend.go | 11 +- consensus/ibft/fork/helper.go | 3 +- consensus/ibft/ibft.go | 17 +- consensus/ibft/proto/ibft_operator.pb.go | 6 +- consensus/ibft/proto/ibft_operator_grpc.pb.go | 38 +- consensus/polybft/README.md | 84 + consensus/polybft/bitmap/bitmap.go | 37 + consensus/polybft/bitmap/bitmap_test.go | 63 + consensus/polybft/block_builder.go | 211 + consensus/polybft/blockchain_wrapper.go | 212 + consensus/polybft/checkpoint_manager.go | 398 ++ consensus/polybft/checkpoint_manager_test.go | 853 +++ consensus/polybft/consensus_metrics.go | 46 + consensus/polybft/consensus_runtime.go | 987 ++++ consensus/polybft/consensus_runtime_test.go | 1176 ++++ consensus/polybft/contracts_initializer.go | 94 + .../contractsapi/artifact/artifacts.go | 56 + .../contractsapi/artifacts-gen/main.go | 85 + .../polybft/contractsapi/bindings-gen/main.go | 331 ++ .../polybft/contractsapi/contractsapi.go | 255 + .../polybft/contractsapi/contractsapi_test.go | 84 + consensus/polybft/contractsapi/decoder.go | 99 + consensus/polybft/contractsapi/gen_sc_data.go | 13 + consensus/polybft/contractsapi/helper.go | 36 + consensus/polybft/contractsapi/init.go | 106 + consensus/polybft/contractsapi/init_test.go | 29 + .../test-contracts/TestL1StateReceiver.json | 86 + .../test-contracts/TestL1StateReceiver.sol | 20 + .../TestWriteBlockMetadata.json | 50 + .../test-contracts/TestWriteBlockMetadata.sol | 19 + consensus/polybft/extra.go | 626 ++ consensus/polybft/extra_test.go | 782 +++ consensus/polybft/fsm.go | 583 ++ consensus/polybft/fsm_test.go | 1411 +++++ consensus/polybft/handlers.go | 27 + consensus/polybft/hash.go | 33 + consensus/polybft/hash_test.go | 39 + consensus/polybft/helpers_test.go | 94 + consensus/polybft/ibft_consensus.go | 43 + consensus/polybft/merkle_tree.go | 153 + consensus/polybft/merkle_tree_test.go | 56 + consensus/polybft/mocks_test.go | 556 ++ consensus/polybft/polybft.go | 509 ++ consensus/polybft/polybft_config.go | 199 + consensus/polybft/polybft_test.go | 266 + consensus/polybft/proposer_calculator.go | 478 ++ consensus/polybft/proposer_calculator_test.go | 710 +++ consensus/polybft/proto/transport.pb.go | 144 + consensus/polybft/proto/transport.proto | 9 + consensus/polybft/runtime_helpers.go | 45 + consensus/polybft/runtime_helpers_test.go | 87 + consensus/polybft/signer/common.go | 55 + consensus/polybft/signer/hash2point.go | 301 + consensus/polybft/signer/hash2point_test.go | 52 + consensus/polybft/signer/private.go | 47 + consensus/polybft/signer/private_test.go | 24 + consensus/polybft/signer/public.go | 110 + consensus/polybft/signer/public_test.go | 60 + consensus/polybft/signer/signature.go | 85 + consensus/polybft/signer/signature_test.go | 147 + .../polybft/signer/testcases/hashToPoint.json | 1 + consensus/polybft/signer/utils.go | 48 + consensus/polybft/signer/utils_test.go | 65 + consensus/polybft/state.go | 850 +++ consensus/polybft/state_stats.go | 156 + consensus/polybft/state_sync_manager.go | 554 ++ consensus/polybft/state_sync_manager_test.go | 490 ++ consensus/polybft/state_test.go | 632 ++ consensus/polybft/state_transaction.go | 218 + consensus/polybft/state_transaction_test.go | 224 + .../statesyncrelayer/state_sync_relayer.go | 225 + .../state_sync_relayer_test.go | 121 + consensus/polybft/system_state.go | 172 + consensus/polybft/system_state_test.go | 280 + consensus/polybft/transport.go | 65 + consensus/polybft/validator_metadata.go | 350 ++ consensus/polybft/validator_metadata_test.go | 269 + consensus/polybft/validator_set.go | 101 + consensus/polybft/validator_set_test.go | 53 + consensus/polybft/validators_snapshot.go | 323 ++ consensus/polybft/validators_snapshot_test.go | 294 + consensus/polybft/wallet/account.go | 103 + consensus/polybft/wallet/account_test.go | 73 + consensus/polybft/wallet/key.go | 75 + consensus/polybft/wallet/key_test.go | 54 + contracts/constants.go | 8 + contracts/system_addresses.go | 27 + core-contracts | 1 + crypto/crypto.go | 27 + docker/README.md | 61 + docker/local/Dockerfile | 1 + docker/local/docker-compose.yml | 35 +- docker/local/polygon-edge.sh | 68 +- e2e-polybft/README.md | 22 + e2e-polybft/bridge_test.go | 676 +++ e2e-polybft/consensus_test.go | 403 ++ e2e-polybft/framework/node.go | 77 + e2e-polybft/framework/test-bridge.go | 123 + e2e-polybft/framework/test-cluster.go | 584 ++ e2e-polybft/framework/test-server.go | 241 + e2e-polybft/helpers_test.go | 79 + e2e-polybft/network_test.go | 38 + e2e-polybft/property_test.go | 49 + e2e-polybft/txpool_test.go | 205 + e2e/framework/ibft.go | 5 +- e2e/framework/testserver.go | 1 + go.mod | 48 +- go.sum | 94 +- helper/common/common.go | 130 +- helper/common/common_test.go | 62 + helper/ipc/ipc_unix.go | 4 +- helper/keystore/keystore.go | 4 +- jsonrpc/bridge_endpoint.go | 26 + jsonrpc/bridge_endpoint_test.go | 54 + jsonrpc/dispatcher.go | 5 + jsonrpc/jsonrpc.go | 1 + jsonrpc/mocks_test.go | 21 + network/e2e_testing.go | 2 +- network/proto/discovery.pb.go | 9 +- network/proto/discovery_grpc.pb.go | 4 + network/proto/identity.pb.go | 118 +- network/proto/identity_grpc.pb.go | 6 +- network/proto/testing.pb.go | 117 +- network/proto/testing_grpc.pb.go | 6 +- scripts/README.md | 36 + scripts/cluster | 112 + secrets/config.go | 4 +- secrets/helper/helper.go | 4 +- secrets/local/local.go | 4 +- secrets/local/local_test.go | 2 +- server/builtin.go | 23 +- server/config.go | 2 + server/proto/system.pb.go | 320 +- server/proto/system_grpc.pb.go | 6 +- server/server.go | 56 +- setup-ci.sh | 15 + state/executor.go | 149 +- state/immutable-trie/snapshot.go | 79 +- state/immutable-trie/state.go | 20 +- state/immutable-trie/storage.go | 22 +- state/immutable-trie/trie.go | 136 +- state/runtime/evm/bitmap.go | 4 +- state/runtime/evm/evm_test.go | 4 + state/runtime/evm/state.go | 12 +- state/runtime/precompiled/base.go | 12 +- state/runtime/precompiled/base_test.go | 3 +- state/runtime/precompiled/blake2f.go | 4 +- state/runtime/precompiled/blake2f_test.go | 4 +- .../precompiled/bls_agg_sigs_verification.go | 107 + .../bls_agg_sigs_verification_test.go | 97 + state/runtime/precompiled/bn256.go | 23 +- state/runtime/precompiled/console.go | 82 + state/runtime/precompiled/console.sol | 5157 +++++++++++++++++ state/runtime/precompiled/modexp.go | 4 +- state/runtime/precompiled/native_transfer.go | 38 + .../precompiled/native_transfer_test.go | 160 + state/runtime/precompiled/precompiled.go | 39 +- state/runtime/runtime.go | 12 +- state/transition_test.go | 2 +- syncer/syncer.go | 11 +- syncer/syncer_test.go | 101 +- syncer/types.go | 6 +- tests/testing.go | 11 + tracker/event_tracker.go | 97 + tracker/event_tracker_test.go | 95 + txpool/proto/operator.pb.go | 189 +- txpool/proto/operator_grpc.pb.go | 6 +- txpool/proto/v1.pb.go | 19 +- txpool/txpool.go | 48 +- txpool/txpool_test.go | 41 + txrelayer/txrelayer.go | 189 + types/buildroot/buildroot.go | 2 +- types/encoding.go | 1 - types/header.go | 5 + types/receipt.go | 6 + types/rlp_encoding_test.go | 77 +- types/rlp_marshal.go | 25 + types/rlp_marshal_storage.go | 30 +- types/rlp_unmarshal.go | 172 +- types/rlp_unmarshal_storage.go | 108 +- types/transaction.go | 34 + types/types.go | 15 +- 253 files changed, 33996 insertions(+), 1111 deletions(-) create mode 100644 .github/workflows/e2e-polybft.yml create mode 100644 command/e2e/params.go create mode 100644 command/e2e/register_validator.go create mode 100644 command/genesis/polybft_params.go create mode 100644 command/polybft/polybft_command.go create mode 100644 command/polybftmanifest/manifest_init.go create mode 100644 command/polybftsecrets/params.go create mode 100644 command/polybftsecrets/result.go create mode 100644 command/polybftsecrets/secrets_init.go create mode 100644 command/results.go create mode 100644 command/rootchain/README.md create mode 100644 command/rootchain/emit/emit.go create mode 100644 command/rootchain/emit/params.go create mode 100644 command/rootchain/emit/result.go create mode 100644 command/rootchain/fund/fund.go create mode 100644 command/rootchain/fund/params.go create mode 100644 command/rootchain/fund/result.go create mode 100644 command/rootchain/helper/metadata.go create mode 100644 command/rootchain/initcontracts/init_contracts.go create mode 100644 command/rootchain/initcontracts/params.go create mode 100644 command/rootchain/initcontracts/result.go create mode 100644 command/rootchain/rootchain.go create mode 100644 command/rootchain/server/params.go create mode 100644 command/rootchain/server/result.go create mode 100644 command/rootchain/server/server.go create mode 100644 command/sidechain/helper.go create mode 100644 command/sidechain/staking/params.go create mode 100644 command/sidechain/staking/stake.go create mode 100644 command/sidechain/unstaking/params.go create mode 100644 command/sidechain/unstaking/unstake.go create mode 100644 command/sidechain/validators/params.go create mode 100644 command/sidechain/validators/validator_info.go create mode 100644 command/sidechain/withdraw/params.go create mode 100644 command/sidechain/withdraw/withdraw.go create mode 100644 consensus/polybft/README.md create mode 100644 consensus/polybft/bitmap/bitmap.go create mode 100644 consensus/polybft/bitmap/bitmap_test.go create mode 100644 consensus/polybft/block_builder.go create mode 100644 consensus/polybft/blockchain_wrapper.go create mode 100644 consensus/polybft/checkpoint_manager.go create mode 100644 consensus/polybft/checkpoint_manager_test.go create mode 100644 consensus/polybft/consensus_metrics.go create mode 100644 consensus/polybft/consensus_runtime.go create mode 100644 consensus/polybft/consensus_runtime_test.go create mode 100644 consensus/polybft/contracts_initializer.go create mode 100644 consensus/polybft/contractsapi/artifact/artifacts.go create mode 100644 consensus/polybft/contractsapi/artifacts-gen/main.go create mode 100644 consensus/polybft/contractsapi/bindings-gen/main.go create mode 100644 consensus/polybft/contractsapi/contractsapi.go create mode 100644 consensus/polybft/contractsapi/contractsapi_test.go create mode 100644 consensus/polybft/contractsapi/decoder.go create mode 100644 consensus/polybft/contractsapi/gen_sc_data.go create mode 100644 consensus/polybft/contractsapi/helper.go create mode 100644 consensus/polybft/contractsapi/init.go create mode 100644 consensus/polybft/contractsapi/init_test.go create mode 100644 consensus/polybft/contractsapi/test-contracts/TestL1StateReceiver.json create mode 100644 consensus/polybft/contractsapi/test-contracts/TestL1StateReceiver.sol create mode 100644 consensus/polybft/contractsapi/test-contracts/TestWriteBlockMetadata.json create mode 100644 consensus/polybft/contractsapi/test-contracts/TestWriteBlockMetadata.sol create mode 100644 consensus/polybft/extra.go create mode 100644 consensus/polybft/extra_test.go create mode 100644 consensus/polybft/fsm.go create mode 100644 consensus/polybft/fsm_test.go create mode 100644 consensus/polybft/handlers.go create mode 100644 consensus/polybft/hash.go create mode 100644 consensus/polybft/hash_test.go create mode 100644 consensus/polybft/helpers_test.go create mode 100644 consensus/polybft/ibft_consensus.go create mode 100644 consensus/polybft/merkle_tree.go create mode 100644 consensus/polybft/merkle_tree_test.go create mode 100644 consensus/polybft/mocks_test.go create mode 100644 consensus/polybft/polybft.go create mode 100644 consensus/polybft/polybft_config.go create mode 100644 consensus/polybft/polybft_test.go create mode 100644 consensus/polybft/proposer_calculator.go create mode 100644 consensus/polybft/proposer_calculator_test.go create mode 100644 consensus/polybft/proto/transport.pb.go create mode 100644 consensus/polybft/proto/transport.proto create mode 100644 consensus/polybft/runtime_helpers.go create mode 100644 consensus/polybft/runtime_helpers_test.go create mode 100644 consensus/polybft/signer/common.go create mode 100644 consensus/polybft/signer/hash2point.go create mode 100644 consensus/polybft/signer/hash2point_test.go create mode 100644 consensus/polybft/signer/private.go create mode 100644 consensus/polybft/signer/private_test.go create mode 100644 consensus/polybft/signer/public.go create mode 100644 consensus/polybft/signer/public_test.go create mode 100644 consensus/polybft/signer/signature.go create mode 100644 consensus/polybft/signer/signature_test.go create mode 100644 consensus/polybft/signer/testcases/hashToPoint.json create mode 100644 consensus/polybft/signer/utils.go create mode 100644 consensus/polybft/signer/utils_test.go create mode 100644 consensus/polybft/state.go create mode 100644 consensus/polybft/state_stats.go create mode 100644 consensus/polybft/state_sync_manager.go create mode 100644 consensus/polybft/state_sync_manager_test.go create mode 100644 consensus/polybft/state_test.go create mode 100644 consensus/polybft/state_transaction.go create mode 100644 consensus/polybft/state_transaction_test.go create mode 100644 consensus/polybft/statesyncrelayer/state_sync_relayer.go create mode 100644 consensus/polybft/statesyncrelayer/state_sync_relayer_test.go create mode 100644 consensus/polybft/system_state.go create mode 100644 consensus/polybft/system_state_test.go create mode 100644 consensus/polybft/transport.go create mode 100644 consensus/polybft/validator_metadata.go create mode 100644 consensus/polybft/validator_metadata_test.go create mode 100644 consensus/polybft/validator_set.go create mode 100644 consensus/polybft/validator_set_test.go create mode 100644 consensus/polybft/validators_snapshot.go create mode 100644 consensus/polybft/validators_snapshot_test.go create mode 100644 consensus/polybft/wallet/account.go create mode 100644 consensus/polybft/wallet/account_test.go create mode 100644 consensus/polybft/wallet/key.go create mode 100644 consensus/polybft/wallet/key_test.go create mode 100644 contracts/constants.go create mode 100644 contracts/system_addresses.go create mode 160000 core-contracts create mode 100644 docker/README.md create mode 100644 e2e-polybft/README.md create mode 100644 e2e-polybft/bridge_test.go create mode 100644 e2e-polybft/consensus_test.go create mode 100644 e2e-polybft/framework/node.go create mode 100644 e2e-polybft/framework/test-bridge.go create mode 100644 e2e-polybft/framework/test-cluster.go create mode 100644 e2e-polybft/framework/test-server.go create mode 100644 e2e-polybft/helpers_test.go create mode 100644 e2e-polybft/network_test.go create mode 100644 e2e-polybft/property_test.go create mode 100644 e2e-polybft/txpool_test.go create mode 100644 helper/common/common_test.go create mode 100644 jsonrpc/bridge_endpoint.go create mode 100644 jsonrpc/bridge_endpoint_test.go create mode 100644 scripts/README.md create mode 100755 scripts/cluster create mode 100755 setup-ci.sh create mode 100644 state/runtime/precompiled/bls_agg_sigs_verification.go create mode 100644 state/runtime/precompiled/bls_agg_sigs_verification_test.go create mode 100644 state/runtime/precompiled/console.go create mode 100644 state/runtime/precompiled/console.sol create mode 100644 state/runtime/precompiled/native_transfer.go create mode 100644 state/runtime/precompiled/native_transfer_test.go create mode 100644 tracker/event_tracker.go create mode 100644 tracker/event_tracker_test.go create mode 100644 txrelayer/txrelayer.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d7d0c720b6..bb9cc64ecc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,6 +3,9 @@ name: Build on: # yamllint disable-line rule:truthy workflow_dispatch: workflow_call: + secrets: + GH_TOKEN_CLONE_PRIVATE: + required: true jobs: go_build: @@ -11,6 +14,10 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v3 + with: + submodules: recursive + token: ${{ secrets.GH_TOKEN_CLONE_PRIVATE }} + - name: Setup Go environment uses: actions/setup-go@v3 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 214d841989..85a84572bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,11 +8,14 @@ jobs: build: name: Build uses: ./.github/workflows/build.yml + secrets: + GH_TOKEN_CLONE_PRIVATE: ${{ secrets.GH_TOKEN_CLONE_PRIVATE }} test: name: Test uses: ./.github/workflows/test.yml needs: build secrets: + GH_TOKEN_CLONE_PRIVATE: ${{ secrets.GH_TOKEN_CLONE_PRIVATE }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} diff --git a/.github/workflows/deploy.devnet.yml b/.github/workflows/deploy.devnet.yml index 84a394ddd4..228e158207 100644 --- a/.github/workflows/deploy.devnet.yml +++ b/.github/workflows/deploy.devnet.yml @@ -23,12 +23,15 @@ jobs: build: name: Build uses: ./.github/workflows/build.yml + secrets: + GH_TOKEN_CLONE_PRIVATE: ${{ secrets.GH_TOKEN_CLONE_PRIVATE }} test: name: Test uses: ./.github/workflows/test.yml needs: build secrets: + GH_TOKEN_CLONE_PRIVATE: ${{ secrets.GH_TOKEN_CLONE_PRIVATE }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} diff --git a/.github/workflows/deploy.testnet.yml b/.github/workflows/deploy.testnet.yml index da68b6308d..df620c3632 100644 --- a/.github/workflows/deploy.testnet.yml +++ b/.github/workflows/deploy.testnet.yml @@ -22,6 +22,8 @@ jobs: build: uses: ./.github/workflows/build.yml name: Build + secrets: + GH_TOKEN_CLONE_PRIVATE: ${{ secrets.GH_TOKEN_CLONE_PRIVATE }} test: uses: ./.github/workflows/test.yml @@ -30,6 +32,7 @@ jobs: secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + GH_TOKEN_CLONE_PRIVATE: ${{ secrets.GH_TOKEN_CLONE_PRIVATE }} container_image_build: name: Build/Push Container Image diff --git a/.github/workflows/deploy_edgenet.yaml b/.github/workflows/deploy_edgenet.yaml index 64df993c4e..72683994d1 100644 --- a/.github/workflows/deploy_edgenet.yaml +++ b/.github/workflows/deploy_edgenet.yaml @@ -1,30 +1,30 @@ ---- -on: # yamllint disable-line rule:truthy - release: - types: - - published - -jobs: - deploy_to_edge_net: - name: Update EdgeNet - runs-on: ubuntu-latest - steps: - - name: Get aws-commander - run: | - wget https://github.com/Trapesys/aws-commander/releases/download/v0.2.0/aws-commander_0.2.0_Linux_x86_64.tar.gz - tar -xf aws-commander_0.2.0_Linux_x86_64.tar.gz - sudo mv aws-commander /usr/local/bin - - - name: Checkout code - uses: actions/checkout@v3 - - - name: Run deployment Ansible - env: - AWS_ACCESS_KEY_ID: ${{ secrets.EDGENET_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.EDGENET_AWS_SECRET_ACCESS_KEY }} - run: > - /usr/local/bin/aws-commander - -instances i-039f7c0b3328a00f8,i-035b9f2d78cfb8ea9,i-00a6c7cb3a213f21f,i-03ac2f42ddcba6120 - -mode ansible - -playbook .github/workflows/ansible/update_edgenet.yaml - -aws-zone us-west-2 +--- +on: # yamllint disable-line rule:truthy + release: + types: + - published + +jobs: + deploy_to_edge_net: + name: Update EdgeNet + runs-on: ubuntu-latest + steps: + - name: Get aws-commander + run: | + wget https://github.com/Trapesys/aws-commander/releases/download/v0.2.0/aws-commander_0.2.0_Linux_x86_64.tar.gz + tar -xf aws-commander_0.2.0_Linux_x86_64.tar.gz + sudo mv aws-commander /usr/local/bin + + - name: Checkout code + uses: actions/checkout@v3 + + - name: Run deployment Ansible + env: + AWS_ACCESS_KEY_ID: ${{ secrets.EDGENET_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.EDGENET_AWS_SECRET_ACCESS_KEY }} + run: > + /usr/local/bin/aws-commander + -instances i-039f7c0b3328a00f8,i-035b9f2d78cfb8ea9,i-00a6c7cb3a213f21f,i-03ac2f42ddcba6120 + -mode ansible + -playbook .github/workflows/ansible/update_edgenet.yaml + -aws-zone us-west-2 diff --git a/.github/workflows/e2e-polybft.yml b/.github/workflows/e2e-polybft.yml new file mode 100644 index 0000000000..e7a7c893e1 --- /dev/null +++ b/.github/workflows/e2e-polybft.yml @@ -0,0 +1,36 @@ +--- +name: PolyBFT E2E tests +on: # yamllint disable-line rule:truthy + push: + branches: + - main + - develop + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + env: + E2E_TESTS: true + E2E_LOGS: true + CI_VERBOSE: true + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + token: ${{ secrets.GH_TOKEN_CLONE_PRIVATE }} + - name: Install Go + uses: actions/setup-go@v3 + with: + go-version: 1.18.x + - name: Compile core contracts + run: make compile-core-contracts + - name: Run tests + run: make test-e2e-polybft + - name: Archive test logs + if: always() + uses: actions/upload-artifact@v3 + with: + name: e2e-logs + path: e2e-logs-*/ + retention-days: 30 diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index c17a7eaaca..6a0062834a 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -15,7 +15,10 @@ jobs: E2E_LOGS: true CI_VERBOSE: true steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + submodules: recursive + token: ${{ secrets.GH_TOKEN_CLONE_PRIVATE }} - name: Install Go uses: actions/setup-go@v3 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 47a95a42c9..2a4bdaba69 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,6 +4,8 @@ on: # yamllint disable-line rule:truthy workflow_dispatch: workflow_call: secrets: + GH_TOKEN_CLONE_PRIVATE: + required: true SONAR_TOKEN: required: true SONAR_HOST_URL: @@ -23,6 +25,10 @@ jobs: uses: actions/checkout@v3 with: submodules: recursive + token: ${{ secrets.GH_TOKEN_CLONE_PRIVATE }} + + - name: Install Dependencies + run: ./setup-ci.sh - name: Run Go Test run: go test -coverprofile coverage.out -timeout 20m `go list ./... | grep -v e2e` diff --git a/.gitignore b/.gitignore index 5468a3c263..56d22509a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ peers.json genesis.json +manifest.json secretsManagerConfig.json *-packr.go config*.json @@ -7,6 +8,7 @@ config*.json bin/ test-chain* +test-rootchain* polygon-edge-chain* .idea @@ -21,8 +23,10 @@ main # exclude build folder artifacts +node_modules # Log files *.log vendor/ +coverage.out diff --git a/.gitmodules b/.gitmodules index 3fe487ff8f..26a6e740c7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "tests/tests"] path = tests/tests - url = https://github.com/ethereum/tests.git + url = https://github.com/ethereum/tests.git + shallow = true +[submodule "core-contracts"] + path = core-contracts + url = https://github.com/0xPolygon/core-contracts + shallow = true + branch = feat-polybft-release diff --git a/.golangci.yml b/.golangci.yml index 145f8a68c1..97cdda689b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -54,6 +54,11 @@ issues: - gosec - unparam - lll + - path: gen_sc_data\.go + linters: + - wsl + - lll + - stylecheck include: - EXC0012 # Exported (.+) should have comment( \(or a comment on this block\))? or be unexported - EXC0013 # Package comment should be of the form "(.+)... diff --git a/Makefile b/Makefile index 263e0aa0d1..33ae44335d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -.PHONY: download-spec-tests -download-spec-tests: +.PHONY: download-submodules +download-submodules: git submodule init git submodule update @@ -11,10 +11,11 @@ bindata: .PHONY: protoc protoc: protoc --go_out=. --go-grpc_out=. ./server/proto/*.proto - protoc --go_out=. --go-grpc_out=. ./protocol/proto/*.proto + #protoc --go_out=. --go-grpc_out=. ./protocol/proto/*.proto protoc --go_out=. --go-grpc_out=. ./network/proto/*.proto protoc --go_out=. --go-grpc_out=. ./txpool/proto/*.proto protoc --go_out=. --go-grpc_out=. ./consensus/ibft/**/*.proto + protoc --go_out=. --go-grpc_out=. ./consensus/polybft/**/*.proto .PHONY: build build: @@ -39,7 +40,7 @@ generate-bsd-licenses: .PHONY: test test: - go test -timeout=20m `go list ./... | grep -v e2e` + go test -coverprofile coverage.out -timeout=20m `go list ./... | grep -v e2e` .PHONY: test-e2e test-e2e: @@ -49,11 +50,18 @@ test-e2e: go build -race -o artifacts/polygon-edge . env EDGE_BINARY=${PWD}/artifacts/polygon-edge go test -v -timeout=30m ./e2e/... -.PHONY: run-local -run-local: - docker-compose -f ./docker/local/docker-compose.yml up -d --build +.PHONY: test-e2e-polybft +test-e2e-polybft: + # We can not build with race because of a bug in boltdb dependency + go build -o artifacts/polygon-edge . + env EDGE_BINARY=${PWD}/artifacts/polygon-edge E2E_TESTS=true E2E_LOGS=true go test -v -timeout=30m ./e2e-polybft/... -.PHONY: stop-local -stop-local: - docker-compose -f ./docker/local/docker-compose.yml stop +.PHONY: compile-core-contracts +compile-core-contracts: + cd core-contracts && npm install && npm run compile + $(MAKE) generate-smart-contract-bindings +.PHONY: generate-smart-contract-bindings +generate-smart-contract-bindings: + go run ./consensus/polybft/contractsapi/artifacts-gen/main.go + go run ./consensus/polybft/contractsapi/bindings-gen/main.go diff --git a/archive/restore.go b/archive/restore.go index 656e0248a7..3cad8d1911 100644 --- a/archive/restore.go +++ b/archive/restore.go @@ -23,7 +23,7 @@ type blockchainInterface interface { GetBlockByNumber(uint64, bool) (*types.Block, bool) GetHashByNumber(uint64) types.Hash WriteBlock(*types.Block, string) error - VerifyFinalizedBlock(*types.Block) error + VerifyFinalizedBlock(*types.Block) (*types.FullBlock, error) } // RestoreChain reads blocks from the archive and write to the chain @@ -78,7 +78,7 @@ func importBlocks(chain blockchainInterface, blockStream *blockStream, progressi nextBlock := firstBlock for { - if err := chain.VerifyFinalizedBlock(nextBlock); err != nil { + if _, err := chain.VerifyFinalizedBlock(nextBlock); err != nil { return err } diff --git a/archive/restore_test.go b/archive/restore_test.go index bfdf5a486f..ceb74b63b7 100644 --- a/archive/restore_test.go +++ b/archive/restore_test.go @@ -55,8 +55,8 @@ func (m *mockChain) WriteBlock(block *types.Block, _ string) error { return nil } -func (m *mockChain) VerifyFinalizedBlock(block *types.Block) error { - return nil +func (m *mockChain) VerifyFinalizedBlock(block *types.Block) (*types.FullBlock, error) { + return &types.FullBlock{Block: block}, nil } func (m *mockChain) SubscribeEvents() blockchain.Subscription { diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 390aad3add..698f0156e4 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -679,44 +679,48 @@ func (b *Blockchain) WriteHeadersWithBodies(headers []*types.Header) error { // outside the method call func (b *Blockchain) VerifyPotentialBlock(block *types.Block) error { // Do just the initial block verification - return b.verifyBlock(block) + _, err := b.verifyBlock(block) + + return err } // VerifyFinalizedBlock verifies that the block is valid by performing a series of checks. // It is assumed that the block status is sealed (committed) -func (b *Blockchain) VerifyFinalizedBlock(block *types.Block) error { +func (b *Blockchain) VerifyFinalizedBlock(block *types.Block) (*types.FullBlock, error) { // Make sure the consensus layer verifies this block header if err := b.consensus.VerifyHeader(block.Header); err != nil { - return fmt.Errorf("failed to verify the header: %w", err) + return nil, fmt.Errorf("failed to verify the header: %w", err) } // Do the initial block verification - if err := b.verifyBlock(block); err != nil { - return err + receipts, err := b.verifyBlock(block) + if err != nil { + return nil, err } - return nil + return &types.FullBlock{Block: block, Receipts: receipts}, nil } // verifyBlock does the base (common) block verification steps by // verifying the block body as well as the parent information -func (b *Blockchain) verifyBlock(block *types.Block) error { +func (b *Blockchain) verifyBlock(block *types.Block) ([]*types.Receipt, error) { // Make sure the block is present if block == nil { - return ErrNoBlock + return nil, ErrNoBlock } // Make sure the block is in line with the parent block if err := b.verifyBlockParent(block); err != nil { - return err + return nil, err } // Make sure the block body data is valid - if err := b.verifyBlockBody(block); err != nil { - return err + receipts, err := b.verifyBlockBody(block) + if err != nil { + return nil, err } - return nil + return receipts, nil } // verifyBlockParent makes sure that the child block is in line @@ -774,7 +778,7 @@ func (b *Blockchain) verifyBlockParent(childBlock *types.Block) error { // - The trie roots match up (state, transactions, receipts, uncles) // - The receipts match up // - The execution result matches up -func (b *Blockchain) verifyBlockBody(block *types.Block) error { +func (b *Blockchain) verifyBlockBody(block *types.Block) ([]*types.Receipt, error) { // Make sure the Uncles root matches up if hash := buildroot.CalculateUncleRoot(block.Uncles); hash != block.Header.Sha3Uncles { b.logger.Error(fmt.Sprintf( @@ -783,7 +787,7 @@ func (b *Blockchain) verifyBlockBody(block *types.Block) error { block.Header.Sha3Uncles, )) - return ErrInvalidSha3Uncles + return nil, ErrInvalidSha3Uncles } // Make sure the transactions root matches up @@ -794,21 +798,21 @@ func (b *Blockchain) verifyBlockBody(block *types.Block) error { block.Header.TxRoot, )) - return ErrInvalidTxRoot + return nil, ErrInvalidTxRoot } // Execute the transactions in the block and grab the result blockResult, executeErr := b.executeBlockTransactions(block) if executeErr != nil { - return fmt.Errorf("unable to execute block transactions, %w", executeErr) + return nil, fmt.Errorf("unable to execute block transactions, %w", executeErr) } // Verify the local execution result with the proposed block data if err := blockResult.verifyBlockResult(block); err != nil { - return fmt.Errorf("unable to verify block execution result, %w", err) + return nil, fmt.Errorf("unable to verify block execution result, %w", err) } - return nil + return blockResult.Receipts, nil } // verifyBlockResult verifies that the block transaction execution result @@ -874,6 +878,68 @@ func (b *Blockchain) executeBlockTransactions(block *types.Block) (*BlockResult, }, nil } +// WriteFullBlock writes a single block to the local blockchain. +// It doesn't do any kind of verification, only commits the block to the DB +// This function is a copy of WriteBlock but with a full block which does not +// require to compute again the Receipts. +func (b *Blockchain) WriteFullBlock(fblock *types.FullBlock, source string) error { + block := fblock.Block + + b.writeLock.Lock() + defer b.writeLock.Unlock() + + if block.Number() <= b.Header().Number { + b.logger.Info("block already inserted", "block", block.Number(), "source", source) + + return nil + } + + header := block.Header + + if err := b.writeBody(block); err != nil { + return err + } + + // Write the header to the chain + evnt := &Event{Source: source} + if err := b.writeHeaderImpl(evnt, header); err != nil { + return err + } + + // write the receipts, do it only after the header has been written. + // Otherwise, a client might ask for a header once the receipt is valid, + // but before it is written into the storage + if err := b.db.WriteReceipts(block.Hash(), fblock.Receipts); err != nil { + return err + } + + // update snapshot + if err := b.consensus.ProcessHeaders([]*types.Header{header}); err != nil { + return err + } + + b.dispatchEvent(evnt) + + // Update the average gas price + b.updateGasPriceAvgWithBlock(block) + + logArgs := []interface{}{ + "number", header.Number, + "txs", len(block.Transactions), + "hash", header.Hash, + "parent", header.ParentHash, + } + + if prevHeader, ok := b.GetHeaderByNumber(header.Number - 1); ok { + diff := header.Timestamp - prevHeader.Timestamp + logArgs = append(logArgs, "generation_time_in_seconds", diff) + } + + b.logger.Info("new block", logArgs...) + + return nil +} + // WriteBlock writes a single block to the local blockchain. // It doesn't do any kind of verification, only commits the block to the DB func (b *Blockchain) WriteBlock(block *types.Block, source string) error { @@ -938,6 +1004,21 @@ func (b *Blockchain) WriteBlock(block *types.Block, source string) error { return nil } +// GetCachedReceipts retrieves cached receipts for given headerHash +func (b *Blockchain) GetCachedReceipts(headerHash types.Hash) ([]*types.Receipt, error) { + receipts, found := b.receiptsCache.Get(headerHash) + if !found { + return nil, fmt.Errorf("failed to retrieve receipts for header hash: %s", headerHash) + } + + extractedReceipts, ok := receipts.([]*types.Receipt) + if !ok { + return nil, errors.New("invalid type assertion for receipts") + } + + return extractedReceipts, nil +} + // extractBlockReceipts extracts the receipts from the passed in block func (b *Blockchain) extractBlockReceipts(block *types.Block) ([]*types.Receipt, error) { // Check the cache for the block receipts @@ -1014,7 +1095,7 @@ func (b *Blockchain) ReadTxLookup(hash types.Hash) (types.Hash, bool) { // return error if the invalid signature found func (b *Blockchain) recoverFromFieldsInBlock(block *types.Block) error { for _, tx := range block.Transactions { - if tx.From != types.ZeroAddress { + if tx.From != types.ZeroAddress || tx.Type == types.StateTx { continue } @@ -1035,7 +1116,7 @@ func (b *Blockchain) recoverFromFieldsInTransactions(transactions []*types.Trans updated := false for _, tx := range transactions { - if tx.From != types.ZeroAddress { + if tx.From != types.ZeroAddress || tx.Type == types.StateTx { continue } diff --git a/blockchain/blockchain_test.go b/blockchain/blockchain_test.go index cc958862f0..1646c49660 100644 --- a/blockchain/blockchain_test.go +++ b/blockchain/blockchain_test.go @@ -1202,7 +1202,8 @@ func TestBlockchain_VerifyBlockBody(t *testing.T) { }, } - assert.ErrorIs(t, blockchain.verifyBlockBody(block), ErrInvalidSha3Uncles) + _, err = blockchain.verifyBlockBody(block) + assert.ErrorIs(t, err, ErrInvalidSha3Uncles) }) t.Run("Invalid Transactions root", func(t *testing.T) { @@ -1219,7 +1220,8 @@ func TestBlockchain_VerifyBlockBody(t *testing.T) { }, } - assert.ErrorIs(t, blockchain.verifyBlockBody(block), ErrInvalidTxRoot) + _, err = blockchain.verifyBlockBody(block) + assert.ErrorIs(t, err, ErrInvalidTxRoot) }) t.Run("Invalid execution result - missing parent", func(t *testing.T) { @@ -1246,7 +1248,8 @@ func TestBlockchain_VerifyBlockBody(t *testing.T) { }, } - assert.ErrorIs(t, blockchain.verifyBlockBody(block), ErrParentNotFound) + _, err = blockchain.verifyBlockBody(block) + assert.ErrorIs(t, err, ErrParentNotFound) }) t.Run("Invalid execution result - unable to fetch block creator", func(t *testing.T) { @@ -1285,7 +1288,8 @@ func TestBlockchain_VerifyBlockBody(t *testing.T) { }, } - assert.ErrorIs(t, blockchain.verifyBlockBody(block), errBlockCreatorNotFound) + _, err = blockchain.verifyBlockBody(block) + assert.ErrorIs(t, err, errBlockCreatorNotFound) }) t.Run("Invalid execution result - unable to execute transactions", func(t *testing.T) { @@ -1327,6 +1331,7 @@ func TestBlockchain_VerifyBlockBody(t *testing.T) { }, } - assert.ErrorIs(t, blockchain.verifyBlockBody(block), errUnableToExecute) + _, err = blockchain.verifyBlockBody(block) + assert.ErrorIs(t, err, errUnableToExecute) }) } diff --git a/blockchain/storage/testing.go b/blockchain/storage/testing.go index 1b3f9e5d27..3fe1fe6ea0 100644 --- a/blockchain/storage/testing.go +++ b/blockchain/storage/testing.go @@ -24,28 +24,28 @@ var ( func TestStorage(t *testing.T, m PlaceholderStorage) { t.Helper() - t.Run("", func(t *testing.T) { + t.Run("testCanonicalChain", func(t *testing.T) { testCanonicalChain(t, m) }) - t.Run("", func(t *testing.T) { + t.Run("testDifficulty", func(t *testing.T) { testDifficulty(t, m) }) - t.Run("", func(t *testing.T) { + t.Run("testHead", func(t *testing.T) { testHead(t, m) }) - t.Run("", func(t *testing.T) { + t.Run("testForks", func(t *testing.T) { testForks(t, m) }) - t.Run("", func(t *testing.T) { + t.Run("testHeader", func(t *testing.T) { testHeader(t, m) }) - t.Run("", func(t *testing.T) { + t.Run("testBody", func(t *testing.T) { testBody(t, m) }) - t.Run("", func(t *testing.T) { + t.Run("testWriteCanonicalHeader", func(t *testing.T) { testWriteCanonicalHeader(t, m) }) - t.Run("", func(t *testing.T) { + t.Run("testReceipts", func(t *testing.T) { testReceipts(t, m) }) } @@ -379,13 +379,11 @@ func testReceipts(t *testing.T, m PlaceholderStorage) { } receipts := []*types.Receipt{r0, r1} - if err := s.WriteReceipts(h.Hash, receipts); err != nil { t.Fatal(err) } found, err := s.ReadReceipts(h.Hash) - if err != nil { t.Fatal(err) } diff --git a/chain/chain.go b/chain/chain.go index 67f216a09d..50bf41e1bc 100644 --- a/chain/chain.go +++ b/chain/chain.go @@ -361,3 +361,13 @@ func importChain(content []byte) (*Chain, error) { return chain, nil } + +// GetGenesisAccountBalance returns balance for genesis account based on its address (expressed in weis). +// If not found in provided allocations map, 0 is returned. +func GetGenesisAccountBalance(address types.Address, allocations map[types.Address]*GenesisAccount) (*big.Int, error) { + if genesisAcc, ok := allocations[address]; ok { + return genesisAcc.Balance, nil + } + + return nil, fmt.Errorf("genesis account %s is not found among genesis allocations", address) +} diff --git a/chain/chain_test.go b/chain/chain_test.go index 88568a019c..db88298784 100644 --- a/chain/chain_test.go +++ b/chain/chain_test.go @@ -2,11 +2,13 @@ package chain import ( "encoding/json" + "fmt" "math/big" "reflect" "testing" "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/require" ) var emptyAddr types.Address @@ -150,3 +152,48 @@ func TestGenesisX(t *testing.T) { }) } } + +func TestGetGenesisAccountBalance(t *testing.T) { + t.Parallel() + + testAddr := types.Address{0x2} + cases := []struct { + name string + address types.Address + allocs map[types.Address]*GenesisAccount + expectedBalance *big.Int + shouldFail bool + }{ + { + name: "Query existing account", + address: testAddr, + allocs: map[types.Address]*GenesisAccount{ + testAddr: {Balance: big.NewInt(50)}, + }, + expectedBalance: big.NewInt(50), + shouldFail: false, + }, + { + name: "Query non-existing account", + address: testAddr, + allocs: nil, + expectedBalance: nil, + shouldFail: true, + }, + } + + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + actualBalance, err := GetGenesisAccountBalance(c.address, c.allocs) + if c.shouldFail { + require.Equal(t, err.Error(), fmt.Errorf("genesis account %s is not found among genesis allocations", c.address).Error()) + } else { + require.NoError(t, err) + } + require.Equal(t, c.expectedBalance, actualBalance) + }) + } +} diff --git a/chain/params.go b/chain/params.go index 57502bf728..64f78e7cd8 100644 --- a/chain/params.go +++ b/chain/params.go @@ -36,6 +36,7 @@ type Forks struct { Constantinople *Fork `json:"constantinople,omitempty"` Petersburg *Fork `json:"petersburg,omitempty"` Istanbul *Fork `json:"istanbul,omitempty"` + London *Fork `json:"london,omitempty"` EIP150 *Fork `json:"EIP150,omitempty"` EIP158 *Fork `json:"EIP158,omitempty"` EIP155 *Fork `json:"EIP155,omitempty"` @@ -65,6 +66,10 @@ func (f *Forks) IsPetersburg(block uint64) bool { return f.active(f.Petersburg, block) } +func (f *Forks) IsLondon(block uint64) bool { + return f.active(f.London, block) +} + func (f *Forks) IsEIP150(block uint64) bool { return f.active(f.EIP150, block) } @@ -84,6 +89,7 @@ func (f *Forks) At(block uint64) ForksInTime { Constantinople: f.active(f.Constantinople, block), Petersburg: f.active(f.Petersburg, block), Istanbul: f.active(f.Istanbul, block), + London: f.active(f.London, block), EIP150: f.active(f.EIP150, block), EIP158: f.active(f.EIP158, block), EIP155: f.active(f.EIP155, block), @@ -112,6 +118,7 @@ type ForksInTime struct { Constantinople, Petersburg, Istanbul, + London, EIP150, EIP158, EIP155 bool @@ -126,4 +133,5 @@ var AllForksEnabled = &Forks{ Constantinople: NewFork(0), Petersburg: NewFork(0), Istanbul: NewFork(0), + London: NewFork(0), } diff --git a/command/cli_output.go b/command/cli_output.go index e12d4eb022..41e04b5e38 100644 --- a/command/cli_output.go +++ b/command/cli_output.go @@ -5,28 +5,50 @@ import ( "os" ) -type CLIOutput struct { +// cliOutput implements OutputFormatter interface by printing the output into std out +type cliOutput struct { commonOutputFormatter } -func newCLIOutput() *CLIOutput { - return &CLIOutput{} +// newCLIOutput is the constructor of cliOutput +func newCLIOutput() *cliOutput { + return &cliOutput{} } -func (cli *CLIOutput) WriteOutput() { +// WriteOutput implements OutputFormatter interface +func (cli *cliOutput) WriteOutput() { if cli.errorOutput != nil { _, _ = fmt.Fprintln(os.Stderr, cli.getErrorOutput()) - return + // return proper error exit code for cli error output + os.Exit(1) } _, _ = fmt.Fprintln(os.Stdout, cli.getCommandOutput()) } -func (cli *CLIOutput) getErrorOutput() string { +// WriteCommandResult implements OutputFormatter interface +func (cli *cliOutput) WriteCommandResult(result CommandResult) { + _, _ = fmt.Fprintln(os.Stdout, result.GetOutput()) +} + +// WriteOutput implements OutputFormatter plus io.Writer interfaces +func (cli *cliOutput) Write(p []byte) (n int, err error) { + return os.Stdout.Write(p) +} + +func (cli *cliOutput) getErrorOutput() string { + if cli.errorOutput == nil { + return "" + } + return cli.errorOutput.Error() } -func (cli *CLIOutput) getCommandOutput() string { +func (cli *cliOutput) getCommandOutput() string { + if cli.commandOutput == nil { + return "" + } + return cli.commandOutput.GetOutput() } diff --git a/command/e2e/params.go b/command/e2e/params.go new file mode 100644 index 0000000000..b893b6b670 --- /dev/null +++ b/command/e2e/params.go @@ -0,0 +1,51 @@ +package e2e + +import ( + "errors" + "fmt" + "os" + + "github.com/0xPolygon/polygon-edge/types" +) + +const ( + dataDirFlag = "data-dir" + registratorDataDirFlag = "registrator-data-dir" + balanceFlag = "balance" + stakeFlag = "stake" +) + +type registerParams struct { + newValidatorDataDir string + registratorValidatorDataDir string + jsonRPCAddr string + balance string + stake string +} + +func (rp *registerParams) validateFlags() error { + if _, err := os.Stat(rp.newValidatorDataDir); errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("provided new validator data directory '%s' doesn't exist", rp.newValidatorDataDir) + } + + if _, err := os.Stat(rp.registratorValidatorDataDir); errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("provided registrator validator data directory '%s' doesn't exist", rp.registratorValidatorDataDir) + } + + balance, err := types.ParseUint256orHex(&rp.balance) + if err != nil { + return fmt.Errorf("provided balance '%s' isn't valid", rp.balance) + } + + stake, err := types.ParseUint256orHex(&rp.stake) + if err != nil { + return fmt.Errorf("provided stake '%s' isn't valid", rp.stake) + } + + if stake.Cmp(balance) > 0 { + return fmt.Errorf("provided stake is greater than funded balance (stake=%s balance=%s)", + stake.String(), balance.String()) + } + + return nil +} diff --git a/command/e2e/register_validator.go b/command/e2e/register_validator.go new file mode 100644 index 0000000000..471d6be592 --- /dev/null +++ b/command/e2e/register_validator.go @@ -0,0 +1,504 @@ +package e2e + +import ( + "context" + "errors" + "fmt" + "math/big" + "time" + + "github.com/0xPolygon/polygon-edge/command/helper" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/crypto" + secretsHelper "github.com/0xPolygon/polygon-edge/secrets/helper" + "github.com/0xPolygon/polygon-edge/types" + "github.com/mitchellh/go-glint" + gc "github.com/mitchellh/go-glint/components" + "github.com/spf13/cobra" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/jsonrpc" +) + +const ( + defaultBalance = "0xD3C21BCECCEDA1000000" // 1e24 + defaultStake = "0x56BC75E2D63100000" // 1e20 +) + +var params registerParams + +func GetCommand() *cobra.Command { + registerCmd := &cobra.Command{ + Use: "register-validator", + Short: "Registers a new validator", + PreRunE: runPreRun, + RunE: runCommand, + } + + setFlags(registerCmd) + + return registerCmd +} + +func setFlags(cmd *cobra.Command) { + cmd.Flags().StringVar( + ¶ms.newValidatorDataDir, + dataDirFlag, + "", + "the directory path where new validator key is stored", + ) + cmd.Flags().StringVar( + ¶ms.registratorValidatorDataDir, + registratorDataDirFlag, + "", + "the directory path where registrator validator key is stored", + ) + + cmd.Flags().StringVar( + ¶ms.balance, + balanceFlag, + defaultBalance, + "balance which is going to be funded to the new validator account", + ) + + cmd.Flags().StringVar( + ¶ms.stake, + stakeFlag, + defaultStake, + "stake represents amount which is going to be staked by the new validator account", + ) + + helper.RegisterJSONRPCFlag(cmd) +} + +func runPreRun(cmd *cobra.Command, _ []string) error { + params.jsonRPCAddr = helper.GetJSONRPCAddress(cmd) + + return params.validateFlags() +} + +func runCommand(cmd *cobra.Command, _ []string) error { + secretsManager, err := secretsHelper.SetupLocalSecretsManager(params.registratorValidatorDataDir) + if err != nil { + return err + } + + existingValidatorAccount, err := wallet.NewAccountFromSecret(secretsManager) + if err != nil { + return err + } + + existingValidatorSender, err := newTxnSender(existingValidatorAccount) + if err != nil { + return err + } + + secretsManager, err = secretsHelper.SetupLocalSecretsManager(params.newValidatorDataDir) + if err != nil { + return err + } + + newValidatorAccount, err := wallet.NewAccountFromSecret(secretsManager) + if err != nil { + return err + } + + newValidatorSender, err := newTxnSender(newValidatorAccount) + if err != nil { + return err + } + + var validator *NewValidator + + steps := []*txnStep{ + { + name: "whitelist", + action: func() asyncTxn { + return whitelist(existingValidatorSender, types.Address(newValidatorAccount.Ecdsa.Address())) + }, + }, + { + name: "fund", + action: func() asyncTxn { + return fund(existingValidatorSender, types.Address(newValidatorAccount.Ecdsa.Address())) + }, + }, + { + name: "register", + action: func() asyncTxn { + return registerValidator(newValidatorSender, newValidatorAccount) + }, + postHook: func(receipt *ethgo.Receipt) error { + if receipt.Status != uint64(types.ReceiptSuccess) { + return errors.New("register validator transaction failed") + } + + for _, log := range receipt.Logs { + if newValidatorEvent.Match(log) { + event, err := newValidatorEvent.ParseLog(log) + if err != nil { + return err + } + + validatorAddr, ok := event["validator"].(ethgo.Address) + if !ok { + return errors.New("type assertions failed for parameter validator") + } + + validator = &NewValidator{ + Validator: validatorAddr, + } + + return nil + } + } + + return errors.New("NewValidator event was not emitted") + }, + }, + { + name: "stake", + action: func() asyncTxn { + return stake(newValidatorSender) + }, + }, + } + + d := glint.New() + go d.Render(context.Background()) + + printStatus := func(done bool) { + comps := []glint.Component{} + + for _, step := range steps { + var status glint.Component + + var opts []glint.StyleOption + + switch step.status { + case txnStepQueued: + status = glint.Text("-") + + case txnStepPending: + status = gc.Spinner() + + opts = append(opts, glint.Color("yellow")) + + case txnStepCompleted: + status = glint.Text("✓") + opts = append(opts, glint.Color("green")) + + case txnStepFailed: + status = glint.Text("✗") + opts = append(opts, glint.Color("red")) + } + + comps = append(comps, glint.Style( + glint.Layout( + status, + glint.Layout(glint.Text(step.name+"...")).MarginLeft(1), + ).Row(), + opts..., + )) + if step.err != nil { + comps = append(comps, glint.Style( + glint.Layout( + status, + glint.Layout(glint.Text("error: "+step.err.Error())).MarginLeft(5), + ).Row(), + opts..., + )) + } + } + + if done { + if validator != nil { + comps = append(comps, glint.Text("\nDone: "+validator.Validator.String()+"\n")) + } else { + comps = append(comps, glint.Text("\nDone\n")) + } + } else { + comps = append(comps, glint.Text("\nWaiting...")) + } + + d.Set(comps...) + } + + for _, step := range steps { + step.status = txnStepPending + + printStatus(false) + + txn := step.action() + receipt, err := txn.Wait() + + if err != nil { + step.status = txnStepFailed + step.err = err + } else { + if receipt.Status == uint64(types.ReceiptFailed) { + step.status = txnStepFailed + } else { + step.status = txnStepCompleted + } + } + + if step.postHook != nil { + err := step.postHook(receipt) + if err != nil { + step.status = txnStepFailed + step.err = err + } + } + + if step.status == txnStepFailed { + break + } + } + + printStatus(true) + + d.RenderFrame() + d.Pause() + + return nil +} + +const ( + defaultGasPrice = 1879048192 // 0x70000000 + defaultGasLimit = 5242880 // 0x500000 +) + +var ( + stakeManager = contracts.ValidatorSetContract + stakeFn = contractsapi.ChildValidatorSet.Abi.Methods["stake"] + whitelistFn = contractsapi.ChildValidatorSet.Abi.Methods["addToWhitelist"] + registerFn = contractsapi.ChildValidatorSet.Abi.Methods["register"] + newValidatorEvent = contractsapi.ChildValidatorSet.Abi.Events["NewValidator"] +) + +type asyncTxn interface { + Wait() (*ethgo.Receipt, error) +} + +type asyncTxnImpl struct { + t *txnSender + hash ethgo.Hash + err error +} + +func (a *asyncTxnImpl) Wait() (*ethgo.Receipt, error) { + // propagate error if there were any + if a.err != nil { + return nil, a.err + } + + return a.t.waitForReceipt(a.hash) +} + +type txnStepStatus int + +const ( + txnStepQueued txnStepStatus = iota + txnStepPending + txnStepCompleted + txnStepFailed +) + +type txnStep struct { + name string + action func() asyncTxn + postHook func(receipt *ethgo.Receipt) error + status txnStepStatus + err error +} + +type txnSender struct { + client *jsonrpc.Client + account *wallet.Account +} + +func (t *txnSender) sendTransaction(txn *types.Transaction) asyncTxn { + if txn.GasPrice == nil { + txn.GasPrice = big.NewInt(defaultGasPrice) + } + + if txn.Gas == 0 { + txn.Gas = defaultGasLimit + } + + if txn.Nonce == 0 { + nonce, err := t.client.Eth().GetNonce(t.account.Ecdsa.Address(), ethgo.Latest) + if err != nil { + return &asyncTxnImpl{err: err} + } + + txn.Nonce = nonce + } + + chainID, err := t.client.Eth().ChainID() + if err != nil { + return &asyncTxnImpl{err: err} + } + + privateKey, err := t.account.GetEcdsaPrivateKey() + if err != nil { + return &asyncTxnImpl{err: err} + } + + signer := crypto.NewEIP155Signer(chainID.Uint64()) + signedTxn, err := signer.SignTx(txn, privateKey) + + if err != nil { + return &asyncTxnImpl{err: err} + } + + txnRaw := signedTxn.MarshalRLP() + hash, err := t.client.Eth().SendRawTransaction(txnRaw) + + if err != nil { + return &asyncTxnImpl{err: err} + } + + return &asyncTxnImpl{hash: hash, t: t} +} + +func (t *txnSender) waitForReceipt(hash ethgo.Hash) (*ethgo.Receipt, error) { + var count uint64 + + for { + receipt, err := t.client.Eth().GetTransactionReceipt(hash) + if err != nil { + if err.Error() != "not found" { + return nil, err + } + } + + if receipt != nil { + return receipt, nil + } + + if count > 1200 { + break + } + + time.Sleep(1000 * time.Millisecond) + count++ + } + + return nil, fmt.Errorf("timeout") +} + +func newDemoClient() (*jsonrpc.Client, error) { + client, err := jsonrpc.NewClient(params.jsonRPCAddr) + if err != nil { + return nil, fmt.Errorf("cannot connect with jsonrpc: %w", err) + } + + return client, err +} + +func newTxnSender(sender *wallet.Account) (*txnSender, error) { + client, err := newDemoClient() + if err != nil { + return nil, err + } + + return &txnSender{ + account: sender, + client: client, + }, nil +} + +func stake(sender *txnSender) asyncTxn { + if stakeFn == nil { + return &asyncTxnImpl{err: errors.New("failed to create stake ABI function")} + } + + input, err := stakeFn.Encode([]interface{}{}) + if err != nil { + return &asyncTxnImpl{err: err} + } + + stake, err := types.ParseUint256orHex(¶ms.stake) + if err != nil { + return &asyncTxnImpl{err: err} + } + + receipt := sender.sendTransaction(&types.Transaction{ + To: &stakeManager, + Input: input, + Value: stake, + }) + + return receipt +} + +func whitelist(sender *txnSender, addr types.Address) asyncTxn { + if whitelistFn == nil { + return &asyncTxnImpl{err: errors.New("failed to create whitelist ABI function")} + } + + input, err := whitelistFn.Encode([]interface{}{ + []types.Address{addr}, + }) + if err != nil { + return &asyncTxnImpl{err: err} + } + + receipt := sender.sendTransaction(&types.Transaction{ + To: &stakeManager, + Input: input, + }) + + return receipt +} + +func fund(sender *txnSender, addr types.Address) asyncTxn { + balance, err := types.ParseUint256orHex(¶ms.balance) + if err != nil { + return &asyncTxnImpl{err: err} + } + + txn := &types.Transaction{ + To: &addr, + Value: balance, + } + + return sender.sendTransaction(txn) +} + +func registerValidator(sender *txnSender, account *wallet.Account) asyncTxn { + if registerFn == nil { + return &asyncTxnImpl{err: errors.New("failed to create register ABI function")} + } + + signature, err := account.Bls.Sign([]byte(contracts.PolyBFTRegisterMessage)) + if err != nil { + return &asyncTxnImpl{err: err} + } + + sigMarshal, err := signature.ToBigInt() + if err != nil { + return &asyncTxnImpl{err: err} + } + + input, err := registerFn.Encode([]interface{}{ + sigMarshal, + account.Bls.PublicKey().ToBigInt(), + }) + if err != nil { + return &asyncTxnImpl{err: err} + } + + return sender.sendTransaction(&types.Transaction{ + To: &stakeManager, + Input: input, + }) +} + +// NewValidator represents validator which is being registered to the chain +type NewValidator struct { + Validator ethgo.Address +} diff --git a/command/genesis/genesis.go b/command/genesis/genesis.go index 6fb35e0bd6..92cf5b712a 100644 --- a/command/genesis/genesis.go +++ b/command/genesis/genesis.go @@ -7,6 +7,7 @@ import ( "github.com/0xPolygon/polygon-edge/command/genesis/predeploy" "github.com/0xPolygon/polygon-edge/command/helper" "github.com/0xPolygon/polygon-edge/consensus/ibft" + "github.com/0xPolygon/polygon-edge/contracts" "github.com/0xPolygon/polygon-edge/helper/common" "github.com/0xPolygon/polygon-edge/validators" "github.com/spf13/cobra" @@ -25,8 +26,6 @@ func GetCommand() *cobra.Command { setFlags(genesisCmd) setLegacyFlags(genesisCmd) - helper.SetRequiredFlags(genesisCmd, params.getRequiredFlags()) - genesisCmd.AddCommand( // genesis predeploy predeploy.GetCommand(), @@ -148,6 +147,60 @@ func setFlags(cmd *cobra.Command) { "the maximum number of validators in the validator set for PoS", ) } + + // PolyBFT + { + cmd.Flags().StringVar( + ¶ms.manifestPath, + manifestPathFlag, + defaultManifestPath, + "the manifest file path, which contains genesis metadata", + ) + + cmd.Flags().IntVar( + ¶ms.validatorSetSize, + validatorSetSizeFlag, + defaultValidatorSetSize, + "the total number of validators", + ) + + cmd.Flags().Uint64Var( + ¶ms.sprintSize, + sprintSizeFlag, + defaultSprintSize, + "the number of block included into a sprint", + ) + + cmd.Flags().DurationVar( + ¶ms.blockTime, + blockTimeFlag, + defaultBlockTime, + "the predefined period which determines block creation frequency", + ) + + cmd.Flags().StringVar( + ¶ms.smartContractsRootPath, + smartContractsRootPathFlag, + contracts.ContractsRootFolder, + "the smart contracts folder", + ) + + cmd.Flags().StringVar( + ¶ms.bridgeJSONRPCAddr, + bridgeFlag, + "", + "the rootchain JSON RPC IP address. If present, node is running in bridge mode.", + ) + + cmd.Flags().Uint64Var( + ¶ms.epochReward, + epochRewardFlag, + defaultEpochReward, + "reward size for block sealing", + ) + + cmd.Flags().Lookup(bridgeFlag).NoOptDefVal = "http://127.0.0.1:8545" + } } // setLegacyFlags sets the legacy flags to preserve backwards compatibility @@ -164,11 +217,13 @@ func setLegacyFlags(cmd *cobra.Command) { _ = cmd.Flags().MarkHidden(chainIDFlagLEGACY) } -func runPreRun(_ *cobra.Command, _ []string) error { +func runPreRun(cmd *cobra.Command, _ []string) error { if err := params.validateFlags(); err != nil { return err } + helper.SetRequiredFlags(cmd, params.getRequiredFlags()) + return params.initRawParams() } @@ -176,7 +231,15 @@ func runCommand(cmd *cobra.Command, _ []string) { outputter := command.InitializeOutputter(cmd) defer outputter.WriteOutput() - if err := params.generateGenesis(); err != nil { + var err error + + if params.isPolyBFTConsensus() { + err = params.generatePolyBftChainConfig() + } else { + err = params.generateGenesis() + } + + if err != nil { outputter.SetError(err) return diff --git a/command/genesis/params.go b/command/genesis/params.go index e5267effdb..1ccce6ac81 100644 --- a/command/genesis/params.go +++ b/command/genesis/params.go @@ -3,6 +3,7 @@ package genesis import ( "errors" "fmt" + "time" "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/command" @@ -10,6 +11,7 @@ import ( "github.com/0xPolygon/polygon-edge/consensus/ibft" "github.com/0xPolygon/polygon-edge/consensus/ibft/fork" "github.com/0xPolygon/polygon-edge/consensus/ibft/signer" + "github.com/0xPolygon/polygon-edge/consensus/polybft" "github.com/0xPolygon/polygon-edge/contracts/staking" stakingHelper "github.com/0xPolygon/polygon-edge/helper/staking" "github.com/0xPolygon/polygon-edge/server" @@ -23,6 +25,7 @@ const ( premineFlag = "premine" chainIDFlag = "chain-id" epochSizeFlag = "epoch-size" + epochRewardFlag = "epoch-reward" blockGasLimitFlag = "block-gas-limit" posFlag = "pos" minValidatorCount = "min-validator-count" @@ -55,8 +58,9 @@ type genesisParams struct { ibftValidatorsRaw []string - chainID uint64 - epochSize uint64 + chainID uint64 + epochSize uint64 + blockGasLimit uint64 isPos bool @@ -72,6 +76,15 @@ type genesisParams struct { consensusEngineConfig map[string]interface{} genesisConfig *chain.Chain + + // PolyBFT + manifestPath string + smartContractsRootPath string + validatorSetSize int + sprintSize uint64 + blockTime time.Duration + bridgeJSONRPCAddr string + epochReward uint64 } func (p *genesisParams) validateFlags() error { @@ -93,7 +106,7 @@ func (p *genesisParams) validateFlags() error { } // Check that the epoch size is correct - if p.epochSize < 2 && server.ConsensusType(p.consensusRaw) == server.IBFTConsensus { + if p.epochSize < 2 && (p.isIBFTConsensus() || p.isPolyBFTConsensus()) { // Epoch size must be greater than 1, so new transactions have a chance to be added to a block. // Otherwise, every block would be an endblock (meaning it will not have any transactions). // Check is placed here to avoid additional parsing if epochSize < 2 @@ -112,6 +125,10 @@ func (p *genesisParams) isIBFTConsensus() bool { return server.ConsensusType(p.consensusRaw) == server.IBFTConsensus } +func (p *genesisParams) isPolyBFTConsensus() bool { + return server.ConsensusType(p.consensusRaw) == server.PolyBFTConsensus +} + func (p *genesisParams) areValidatorsSetManually() bool { return len(p.ibftValidatorsRaw) != 0 } @@ -121,14 +138,22 @@ func (p *genesisParams) areValidatorsSetByPrefix() bool { } func (p *genesisParams) getRequiredFlags() []string { - return []string{ - command.BootnodeFlag, + if p.isIBFTConsensus() { + return []string{ + command.BootnodeFlag, + } } + + return []string{} } func (p *genesisParams) initRawParams() error { p.consensus = server.ConsensusType(p.consensusRaw) + if p.consensus == server.PolyBFTConsensus { + return nil + } + if err := p.initIBFTValidatorType(); err != nil { return err } @@ -312,8 +337,15 @@ func (p *genesisParams) initGenesisConfig() error { chainConfig.Genesis.Alloc[staking.AddrStakingContract] = stakingAccount } - if err := fillPremineMap(chainConfig.Genesis.Alloc, p.premine); err != nil { - return err + for _, premineRaw := range p.premine { + premineInfo, err := parsePremineInfo(premineRaw) + if err != nil { + return err + } + + chainConfig.Genesis.Alloc[premineInfo.address] = &chain.GenesisAccount{ + Balance: premineInfo.balance, + } } p.genesisConfig = chainConfig @@ -343,6 +375,6 @@ func (p *genesisParams) predeployStakingSC() (*chain.GenesisAccount, error) { func (p *genesisParams) getResult() command.CommandResult { return &GenesisResult{ - Message: fmt.Sprintf("Genesis written to %s\n", p.genesisPath), + Message: fmt.Sprintf("%s\nGenesis written to %s\n", polybft.DisclaimerMessage, p.genesisPath), } } diff --git a/command/genesis/polybft_params.go b/command/genesis/polybft_params.go new file mode 100644 index 0000000000..f9997c61bb --- /dev/null +++ b/command/genesis/polybft_params.go @@ -0,0 +1,257 @@ +package genesis + +import ( + "bytes" + "errors" + "fmt" + "math/big" + "sort" + "time" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi/artifact" + + "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/command" + "github.com/0xPolygon/polygon-edge/command/helper" + + "github.com/0xPolygon/polygon-edge/consensus/polybft" + "github.com/0xPolygon/polygon-edge/consensus/polybft/bitmap" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/server" + "github.com/0xPolygon/polygon-edge/types" +) + +const ( + smartContractsRootPathFlag = "contracts-path" + manifestPathFlag = "manifest" + validatorSetSizeFlag = "validator-set-size" + sprintSizeFlag = "sprint-size" + blockTimeFlag = "block-time" + bridgeFlag = "bridge-json-rpc" + + defaultManifestPath = "./manifest.json" + defaultEpochSize = uint64(10) + defaultSprintSize = uint64(5) + defaultValidatorSetSize = 100 + defaultBlockTime = 2 * time.Second + defaultBridge = false + defaultEpochReward = 1 + + bootnodePortStart = 30301 +) + +var ( + errNoGenesisValidators = errors.New("genesis validators aren't provided") +) + +// generatePolyBftChainConfig creates and persists polybft chain configuration to the provided file path +func (p *genesisParams) generatePolyBftChainConfig() error { + // load manifest file + manifest, err := polybft.LoadManifest(p.manifestPath) + if err != nil { + return fmt.Errorf("failed to load manifest file from provided path '%s': %w", p.manifestPath, err) + } + + if len(manifest.GenesisValidators) == 0 { + return errNoGenesisValidators + } + + var bridge *polybft.BridgeConfig + + // populate bridge configuration + if p.bridgeJSONRPCAddr != "" && manifest.RootchainConfig != nil { + bridge = manifest.RootchainConfig.ToBridgeConfig() + bridge.JSONRPCEndpoint = p.bridgeJSONRPCAddr + } + + polyBftConfig := &polybft.PolyBFTConfig{ + InitialValidatorSet: manifest.GenesisValidators, + BlockTime: p.blockTime, + EpochSize: p.epochSize, + SprintSize: p.sprintSize, + EpochReward: p.epochReward, + // use 1st account as governance address + Governance: manifest.GenesisValidators[0].Address, + Bridge: bridge, + ValidatorSetAddr: contracts.ValidatorSetContract, + StateReceiverAddr: contracts.StateReceiverContract, + } + + chainConfig := &chain.Chain{ + Name: p.name, + Params: &chain.Params{ + ChainID: int(p.chainID), + Forks: chain.AllForksEnabled, + Engine: map[string]interface{}{ + string(server.PolyBFTConsensus): polyBftConfig, + }, + }, + Bootnodes: p.bootnodes, + } + + premineInfos := make([]*premineInfo, len(manifest.GenesisValidators)) + validatorPreminesMap := make(map[types.Address]int, len(manifest.GenesisValidators)) + totalStake := big.NewInt(0) + + for i, validator := range manifest.GenesisValidators { + // populate premine info for validator accounts + premineInfo := &premineInfo{address: validator.Address, balance: validator.Balance} + premineInfos[i] = premineInfo + validatorPreminesMap[premineInfo.address] = i + + // TODO: @Stefan-Ethernal change this to Stake when https://github.com/0xPolygon/polygon-edge/pull/1137 gets merged + // increment total stake + totalStake.Add(totalStake, validator.Balance) + } + + // deploy genesis contracts + allocs, err := p.deployContracts(totalStake) + if err != nil { + return err + } + + // either premine non-validator or override validator accounts balance + for _, premine := range p.premine { + premineInfo, err := parsePremineInfo(premine) + if err != nil { + return err + } + + if i, ok := validatorPreminesMap[premineInfo.address]; ok { + premineInfos[i] = premineInfo + } else { + premineInfos = append(premineInfos, premineInfo) //nolint:makezero + } + } + + // premine accounts + for _, premine := range premineInfos { + allocs[premine.address] = &chain.GenesisAccount{ + Balance: premine.balance, + } + } + + validatorMetadata := make([]*polybft.ValidatorMetadata, len(manifest.GenesisValidators)) + + for i, validator := range manifest.GenesisValidators { + // update balance of genesis validator, because it could be changed via premine flag + balance, err := chain.GetGenesisAccountBalance(validator.Address, allocs) + if err != nil { + return err + } + + validator.Balance = balance + + // create validator metadata instance + metadata, err := validator.ToValidatorMetadata() + if err != nil { + return err + } + + validatorMetadata[i] = metadata + + // set genesis validators as boot nodes if boot nodes not provided via CLI + if len(p.bootnodes) == 0 { + bootNodeMultiAddr := fmt.Sprintf("/ip4/%s/tcp/%d/p2p/%s", "127.0.0.1", bootnodePortStart+i, validator.NodeID) + chainConfig.Bootnodes = append(chainConfig.Bootnodes, bootNodeMultiAddr) + } + } + + genesisExtraData, err := generateExtraDataPolyBft(validatorMetadata) + if err != nil { + return err + } + + // populate genesis parameters + chainConfig.Genesis = &chain.Genesis{ + GasLimit: p.blockGasLimit, + Difficulty: 0, + Alloc: allocs, + ExtraData: genesisExtraData, + GasUsed: command.DefaultGenesisGasUsed, + Mixhash: polybft.PolyBFTMixDigest, + } + + return helper.WriteGenesisConfigToDisk(chainConfig, params.genesisPath) +} + +func (p *genesisParams) deployContracts(totalStake *big.Int) (map[types.Address]*chain.GenesisAccount, error) { + genesisContracts := []struct { + name string + relativePath string + address types.Address + }{ + { + // Validator contract + name: "ChildValidatorSet", + relativePath: "child/ChildValidatorSet.sol", + address: contracts.ValidatorSetContract, + }, + { + // State receiver contract + name: "StateReceiver", + relativePath: "child/StateReceiver.sol", + address: contracts.StateReceiverContract, + }, + { + // Native Token contract (Matic ERC-20) + name: "MRC20", + relativePath: "child/MRC20.sol", + address: contracts.NativeTokenContract, + }, + { + // BLS contract + name: "BLS", + relativePath: "common/BLS.sol", + address: contracts.BLSContract, + }, + { + // Merkle contract + name: "Merkle", + relativePath: "common/Merkle.sol", + address: contracts.MerkleContract, + }, + { + // L2StateSender contract + name: "L2StateSender", + relativePath: "child/L2StateSender.sol", + address: contracts.L2StateSenderContract, + }, + } + + allocations := make(map[types.Address]*chain.GenesisAccount, len(genesisContracts)) + + for _, contract := range genesisContracts { + artifact, err := artifact.ReadArtifact(p.smartContractsRootPath, contract.relativePath, contract.name) + if err != nil { + return nil, err + } + + allocations[contract.address] = &chain.GenesisAccount{ + Balance: big.NewInt(0), + Code: artifact.DeployedBytecode, + } + } + + // ChildValidatorSet must have funds pre-allocated, because of withdrawal workflow + allocations[contracts.ValidatorSetContract].Balance = totalStake + + return allocations, nil +} + +// generateExtraDataPolyBft populates Extra with specific fields required for polybft consensus protocol +func generateExtraDataPolyBft(validators []*polybft.ValidatorMetadata) ([]byte, error) { + delta := &polybft.ValidatorSetDelta{ + Added: validators, + Removed: bitmap.Bitmap{}, + } + + // Order validators based on its addresses + sort.Slice(delta.Added, func(i, j int) bool { + return bytes.Compare(delta.Added[i].Address[:], delta.Added[j].Address[:]) < 0 + }) + + extra := polybft.Extra{Validators: delta, Checkpoint: &polybft.CheckpointData{}} + + return append(make([]byte, polybft.ExtraVanity), extra.MarshalRLPTo(nil)...), nil +} diff --git a/command/genesis/utils.go b/command/genesis/utils.go index ca8c0b4969..43376183d7 100644 --- a/command/genesis/utils.go +++ b/command/genesis/utils.go @@ -1,13 +1,24 @@ package genesis import ( + "encoding/hex" "fmt" + "io/ioutil" + "math/big" "os" + "path/filepath" + "sort" + "strconv" "strings" - "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/command" + "github.com/0xPolygon/polygon-edge/consensus/polybft" + "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" + "github.com/0xPolygon/polygon-edge/secrets" + "github.com/0xPolygon/polygon-edge/secrets/helper" + "github.com/0xPolygon/polygon-edge/secrets/local" "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" ) const ( @@ -31,6 +42,11 @@ func (g *GenesisGenError) GetType() string { return g.errorType } +type premineInfo struct { + address types.Address + balance *big.Int +} + // verifyGenesisExistence checks if the genesis file at the specified path is present func verifyGenesisExistence(genesisPath string) *GenesisGenError { _, err := os.Stat(genesisPath) @@ -51,33 +67,114 @@ func verifyGenesisExistence(genesisPath string) *GenesisGenError { return nil } -// fillPremineMap fills the premine map for the genesis.json file with passed in balances and accounts -func fillPremineMap( - premineMap map[types.Address]*chain.GenesisAccount, - premine []string, -) error { - for _, prem := range premine { - var addr types.Address - - val := command.DefaultPremineBalance - - if indx := strings.Index(prem, ":"); indx != -1 { - // : - addr, val = types.StringToAddress(prem[:indx]), prem[indx+1:] - } else { - // - addr = types.StringToAddress(prem) +// parsePremineInfo parses provided premine information and returns premine address and premine balance +func parsePremineInfo(premineInfoRaw string) (*premineInfo, error) { + address := types.ZeroAddress + val := command.DefaultPremineBalance + + if delimiterIdx := strings.Index(premineInfoRaw, ":"); delimiterIdx != -1 { + // : + address, val = types.StringToAddress(premineInfoRaw[:delimiterIdx]), premineInfoRaw[delimiterIdx+1:] + } else { + // + address = types.StringToAddress(premineInfoRaw) + } + + amount, err := types.ParseUint256orHex(&val) + if err != nil { + return nil, fmt.Errorf("failed to parse amount %s: %w", val, err) + } + + return &premineInfo{address: address, balance: amount}, nil +} + +// GetValidatorKeyFiles returns file names which has validator secrets +func GetValidatorKeyFiles(rootDir, filePrefix string) ([]string, error) { + if rootDir == "" { + rootDir = "." + } + + files, err := ioutil.ReadDir(rootDir) + if err != nil { + return nil, err + } + + matchedFiles := 0 + fileNames := make([]string, len(files)) + + for _, file := range files { + fileName := file.Name() + if file.IsDir() && strings.HasPrefix(fileName, filePrefix) { + fileNames[matchedFiles] = fileName + matchedFiles++ } + } + // reslice to remove empty entries + fileNames = fileNames[:matchedFiles] + + // we must sort files by number after the prefix not by name string + sort.Slice(fileNames, func(i, j int) bool { + first := strings.TrimPrefix(fileNames[i], filePrefix) + second := strings.TrimPrefix(fileNames[j], filePrefix) + num1, _ := strconv.Atoi(strings.TrimLeft(first, "-")) + num2, _ := strconv.Atoi(strings.TrimLeft(second, "-")) + + return num1 < num2 + }) - amount, err := types.ParseUint256orHex(&val) + return fileNames, nil +} + +// ReadValidatorsByPrefix reads validators secrets on a given root directory and with given folder prefix +func ReadValidatorsByPrefix(dir, prefix string) ([]*polybft.Validator, error) { + validatorKeyFiles, err := GetValidatorKeyFiles(dir, prefix) + if err != nil { + return nil, err + } + + validators := make([]*polybft.Validator, len(validatorKeyFiles)) + + for i, file := range validatorKeyFiles { + path := filepath.Join(dir, file) + + account, nodeID, err := getSecrets(path) if err != nil { - return fmt.Errorf("failed to parse amount %s: %w", val, err) + return nil, err } - premineMap[addr] = &chain.GenesisAccount{ - Balance: amount, + validator := &polybft.Validator{ + Address: types.Address(account.Ecdsa.Address()), + BlsKey: hex.EncodeToString(account.Bls.PublicKey().Marshal()), + NodeID: nodeID, } + validators[i] = validator } - return nil + return validators, nil +} + +func getSecrets(directory string) (*wallet.Account, string, error) { + baseConfig := &secrets.SecretsManagerParams{ + Logger: hclog.NewNullLogger(), + Extra: map[string]interface{}{ + secrets.Path: directory, + }, + } + + localManager, err := local.SecretsManagerFactory(nil, baseConfig) + if err != nil { + return nil, "", fmt.Errorf("unable to instantiate local secrets manager, %w", err) + } + + nodeID, err := helper.LoadNodeID(localManager) + if err != nil { + return nil, "", err + } + + account, err := wallet.NewAccountFromSecret(localManager) + if err != nil { + return nil, "", err + } + + return account, nodeID, nil } diff --git a/command/helper/helper.go b/command/helper/helper.go index 0f3cee4fc0..e19b25d441 100644 --- a/command/helper/helper.go +++ b/command/helper/helper.go @@ -6,7 +6,6 @@ import ( "fmt" "net" "net/url" - "os" "time" "github.com/0xPolygon/polygon-edge/chain" @@ -239,7 +238,7 @@ func WriteGenesisConfigToDisk(genesisConfig *chain.Chain, genesisPath string) er return fmt.Errorf("failed to generate genesis: %w", err) } - if err := os.WriteFile(genesisPath, data, os.ModePerm); err != nil { + if err := common.SaveFileSafe(genesisPath, data, 0660); err != nil { return fmt.Errorf("failed to write genesis: %w", err) } diff --git a/command/json_output.go b/command/json_output.go index 68d6821b6f..8d8b1806d2 100644 --- a/command/json_output.go +++ b/command/json_output.go @@ -6,11 +6,18 @@ import ( "os" ) -type JSONOutput struct { +// cliOutput implements OutputFormatter interface by printing the output into std out in JSON format +type jsonOutput struct { commonOutputFormatter } -func (jo *JSONOutput) WriteOutput() { +// newJSONOutput is the constructor of jsonOutput +func newJSONOutput() *jsonOutput { + return &jsonOutput{} +} + +// WriteOutput implements OutputFormatter interface +func (jo *jsonOutput) WriteOutput() { if jo.errorOutput != nil { _, _ = fmt.Fprintln(os.Stderr, jo.getErrorOutput()) @@ -20,11 +27,21 @@ func (jo *JSONOutput) WriteOutput() { _, _ = fmt.Fprintln(os.Stdout, jo.getCommandOutput()) } -func newJSONOutput() *JSONOutput { - return &JSONOutput{} +// WriteCommandResult implements OutputFormatter interface +func (jo *jsonOutput) WriteCommandResult(result CommandResult) { + _, _ = fmt.Fprintln(os.Stdout, result.GetOutput()) +} + +// WriteOutput implements OutputFormatter plus io.Writer interfaces +func (jo *jsonOutput) Write(p []byte) (n int, err error) { + return os.Stdout.Write(p) } -func (jo *JSONOutput) getErrorOutput() string { +func (jo *jsonOutput) getErrorOutput() string { + if jo.errorOutput == nil { + return "" + } + return marshalJSONToString( struct { Err string `json:"error"` @@ -34,7 +51,11 @@ func (jo *JSONOutput) getErrorOutput() string { ) } -func (jo *JSONOutput) getCommandOutput() string { +func (jo *jsonOutput) getCommandOutput() string { + if jo.commandOutput == nil { + return "" + } + return marshalJSONToString(jo.commandOutput) } diff --git a/command/output.go b/command/output.go index 1afce3d9a5..6a0b6c51fb 100644 --- a/command/output.go +++ b/command/output.go @@ -7,30 +7,26 @@ import ( // OutputFormatter is the standardized interface all output formatters // should use type OutputFormatter interface { - // getErrorOutput returns the CLI command error - getErrorOutput() string - - // getCommandOutput returns the CLI command output - getCommandOutput() string - // SetError sets the encountered error SetError(err error) // SetCommandResult sets the result of the command execution SetCommandResult(result CommandResult) - // WriteOutput writes the result / error output + // WriteOutput writes the previously set result / error output WriteOutput() + + // WriteCommandResult immediately writes the given command result without waiting for WriteOutput func call. + WriteCommandResult(result CommandResult) + + // Write extends io.Writer interface + Write(p []byte) (n int, err error) } type CommandResult interface { GetOutput() string } -func shouldOutputJSON(baseCmd *cobra.Command) bool { - return baseCmd.Flag(JSONOutputFlag).Changed -} - func InitializeOutputter(cmd *cobra.Command) OutputFormatter { if shouldOutputJSON(cmd) { return newJSONOutput() @@ -38,3 +34,7 @@ func InitializeOutputter(cmd *cobra.Command) OutputFormatter { return newCLIOutput() } + +func shouldOutputJSON(baseCmd *cobra.Command) bool { + return baseCmd.Flag(JSONOutputFlag).Changed +} diff --git a/command/polybft/polybft_command.go b/command/polybft/polybft_command.go new file mode 100644 index 0000000000..328c6641a8 --- /dev/null +++ b/command/polybft/polybft_command.go @@ -0,0 +1,27 @@ +package polybft + +import ( + "github.com/0xPolygon/polygon-edge/command/e2e" + "github.com/0xPolygon/polygon-edge/command/sidechain/staking" + "github.com/0xPolygon/polygon-edge/command/sidechain/unstaking" + "github.com/0xPolygon/polygon-edge/command/sidechain/validators" + "github.com/0xPolygon/polygon-edge/command/sidechain/withdraw" + "github.com/spf13/cobra" +) + +func GetCommand() *cobra.Command { + polybftCmd := &cobra.Command{ + Use: "polybft", + Short: "Polybft command", + } + + polybftCmd.AddCommand( + staking.GetCommand(), + unstaking.GetCommand(), + withdraw.GetCommand(), + validators.GetCommand(), + e2e.GetCommand(), + ) + + return polybftCmd +} diff --git a/command/polybftmanifest/manifest_init.go b/command/polybftmanifest/manifest_init.go new file mode 100644 index 0000000000..6aaf1d15b1 --- /dev/null +++ b/command/polybftmanifest/manifest_init.go @@ -0,0 +1,210 @@ +package polybftmanifest + +import ( + "bytes" + "errors" + "fmt" + "os" + "path" + "strings" + + "github.com/0xPolygon/polygon-edge/command" + "github.com/0xPolygon/polygon-edge/command/genesis" + "github.com/0xPolygon/polygon-edge/consensus/polybft" + "github.com/0xPolygon/polygon-edge/types" + "github.com/spf13/cobra" +) + +const ( + manifestPathFlag = "path" + premineValidatorsFlag = "premine-validators" + validatorsFlag = "validators" + validatorsPathFlag = "validators-path" + validatorsPrefixFlag = "validators-prefix" + + defaultValidatorPrefixPath = "test-chain-" + defaultManifestPath = "./manifest.json" + + nodeIDLength = 53 + ecdsaAddressLength = 42 + blsKeyLength = 2 +) + +var ( + params = &manifestInitParams{} +) + +func GetCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "manifest", + Short: "Initializes manifest file. It is applicable only to polybft consensus protocol.", + PreRunE: runPreRun, + Run: runCommand, + } + + setFlags(cmd) + + return cmd +} + +func runPreRun(_ *cobra.Command, _ []string) error { + return params.validateFlags() +} + +func setFlags(cmd *cobra.Command) { + cmd.Flags().StringVar( + ¶ms.manifestPath, + manifestPathFlag, + defaultManifestPath, + "the file path where manifest file is going to be stored", + ) + + cmd.Flags().StringVar( + ¶ms.validatorsPath, + validatorsPathFlag, + "./", + "root path containing polybft validator keys", + ) + + cmd.Flags().StringVar( + ¶ms.validatorsPrefixPath, + validatorsPrefixFlag, + defaultValidatorPrefixPath, + "folder prefix names for polybft validator keys", + ) + + cmd.Flags().StringArrayVar( + ¶ms.validators, + validatorsFlag, + []string{}, + "validators defined by user (format: ::)", + ) + + cmd.Flags().StringVar( + ¶ms.premineValidators, + premineValidatorsFlag, + command.DefaultPremineBalance, + "the amount which will be pre-mined to all the validators", + ) + + cmd.MarkFlagsMutuallyExclusive(validatorsFlag, validatorsPathFlag) + cmd.MarkFlagsMutuallyExclusive(validatorsFlag, validatorsPrefixFlag) +} + +func runCommand(cmd *cobra.Command, _ []string) { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + validators, err := params.getValidatorAccounts() + if err != nil { + outputter.SetError(fmt.Errorf("failed to get validator accounts: %w", err)) + + return + } + + manifest := &polybft.Manifest{GenesisValidators: validators} + if err = manifest.Save(params.manifestPath); err != nil { + outputter.SetError(fmt.Errorf("failed to save manifest file '%s': %w", params.manifestPath, err)) + + return + } + + outputter.SetCommandResult(params.getResult()) +} + +type manifestInitParams struct { + manifestPath string + validatorsPath string + validatorsPrefixPath string + premineValidators string + validators []string +} + +func (p *manifestInitParams) validateFlags() error { + if _, err := os.Stat(p.validatorsPath); errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("provided validators path '%s' doesn't exist", p.validatorsPath) + } + + if _, err := types.ParseUint256orHex(&p.premineValidators); err != nil { + return fmt.Errorf("invalid premine validators balance provided '%s': %w", p.premineValidators, err) + } + + return nil +} + +// getValidatorAccounts gathers validator accounts info either from CLI or from provided local storage +func (p *manifestInitParams) getValidatorAccounts() ([]*polybft.Validator, error) { + balance, err := types.ParseUint256orHex(¶ms.premineValidators) + if err != nil { + return nil, fmt.Errorf("provided invalid premine validators balance: %s", params.premineValidators) + } + + if len(p.validators) > 0 { + validators := make([]*polybft.Validator, len(p.validators)) + for i, validator := range p.validators { + parts := strings.Split(validator, ":") + + if len(parts) != 3 { + return nil, fmt.Errorf("expected 3 parts provided in the following format "+ + ", but got %d part(s)", + len(parts)) + } + + if len(parts[0]) != nodeIDLength { + return nil, fmt.Errorf("invalid node id: %s", parts[0]) + } + + if len(parts[1]) != ecdsaAddressLength { + return nil, fmt.Errorf("invalid address: %s", parts[1]) + } + + if len(parts[2]) < blsKeyLength { + return nil, fmt.Errorf("invalid bls key: %s", parts[2]) + } + + validators[i] = &polybft.Validator{ + NodeID: parts[0], + Address: types.StringToAddress(parts[1]), + BlsKey: parts[2], + Balance: balance, + } + } + + return validators, nil + } + + validatorsPath := p.validatorsPath + if validatorsPath == "" { + validatorsPath = path.Dir(p.manifestPath) + } + + validators, err := genesis.ReadValidatorsByPrefix(validatorsPath, p.validatorsPrefixPath) + if err != nil { + return nil, err + } + + for _, v := range validators { + v.Balance = balance + } + + return validators, nil +} + +func (p *manifestInitParams) getResult() command.CommandResult { + return &result{ + message: fmt.Sprintf("Manifest file written to %s\n", p.manifestPath), + } +} + +type result struct { + message string +} + +func (r *result) GetOutput() string { + var buffer bytes.Buffer + + buffer.WriteString("\n[MANIFEST INITIALIZATION SUCCESS]\n") + buffer.WriteString(r.message) + + return buffer.String() +} diff --git a/command/polybftsecrets/params.go b/command/polybftsecrets/params.go new file mode 100644 index 0000000000..4d205e9e31 --- /dev/null +++ b/command/polybftsecrets/params.go @@ -0,0 +1,242 @@ +package polybftsecrets + +import ( + "encoding/hex" + "errors" + "fmt" + + "github.com/0xPolygon/polygon-edge/command" + "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" + "github.com/0xPolygon/polygon-edge/secrets" + "github.com/0xPolygon/polygon-edge/secrets/helper" + "github.com/0xPolygon/polygon-edge/types" + "github.com/spf13/cobra" +) + +const ( + dataPathFlag = "data-dir" + configFlag = "config" + accountFlag = "account" + privateKeyFlag = "private" + networkFlag = "network" + numFlag = "num" + + // maxInitNum is the maximum value for "num" flag + maxInitNum = 30 + + insecureLocalStore = "insecure" +) + +var ( + errInvalidNum = fmt.Errorf("num flag value should be between 1 and %d", maxInitNum) + errInvalidConfig = errors.New("invalid secrets configuration") + errInvalidParams = errors.New("no config file or data directory passed in") + errUnsupportedType = errors.New("unsupported secrets manager") + errSecureLocalStoreNotImplemented = errors.New( + "use a secrets backend, or supply an --insecure flag " + + "to store the private keys locally on the filesystem, " + + "avoid doing so in production") +) + +type initParams struct { + dataPath string + configPath string + + generatesAccount bool + generatesNetwork bool + + printPrivateKey bool + + numberOfSecrets int + + insecureLocalStore bool +} + +func (ip *initParams) validateFlags() error { + if ip.numberOfSecrets < 1 || ip.numberOfSecrets > maxInitNum { + return errInvalidNum + } + + if ip.dataPath == "" && ip.configPath == "" { + return errInvalidParams + } + + return nil +} + +func (ip *initParams) setFlags(cmd *cobra.Command) { + cmd.Flags().StringVar( + &ip.dataPath, + dataPathFlag, + "", + "the directory for the Polygon Edge data if the local FS is used", + ) + + cmd.Flags().StringVar( + &ip.configPath, + configFlag, + "", + "the path to the SecretsManager config file, "+ + "if omitted, the local FS secrets manager is used", + ) + + cmd.Flags().IntVar( + &ip.numberOfSecrets, + numFlag, + 1, + "the flag indicating how many secrets should be created, only for the local FS", + ) + + // Don't accept data-dir and config flags because they are related to different secrets managers. + // data-dir is about the local FS as secrets storage, config is about remote secrets manager. + cmd.MarkFlagsMutuallyExclusive(dataPathFlag, configFlag) + + // num flag should be used with data-dir flag only so it should not be used with config flag. + cmd.MarkFlagsMutuallyExclusive(numFlag, configFlag) + + cmd.Flags().BoolVar( + &ip.generatesAccount, + accountFlag, + true, + "the flag indicating whether new account is created", + ) + + cmd.Flags().BoolVar( + &ip.generatesNetwork, + networkFlag, + true, + "the flag indicating whether new Network key is created", + ) + + cmd.Flags().BoolVar( + &ip.printPrivateKey, + privateKeyFlag, + false, + "the flag indicating whether Private key is printed", + ) + + cmd.Flags().BoolVar( + &ip.insecureLocalStore, + insecureLocalStore, + false, + "the flag indicating should the secrets stored on the local storage be encrypted", + ) +} + +func (ip *initParams) Execute() (Results, error) { + results := make(Results, ip.numberOfSecrets) + + for i := 0; i < ip.numberOfSecrets; i++ { + configDir, dataDir := ip.configPath, ip.dataPath + + if ip.numberOfSecrets > 1 { + dataDir = fmt.Sprintf("%s%d", ip.dataPath, i+1) + } + + if configDir != "" && ip.numberOfSecrets > 1 { + configDir = fmt.Sprintf("%s%d", ip.configPath, i+1) + } + + secretManager, err := getSecretsManager(dataDir, configDir, ip.insecureLocalStore) + if err != nil { + return results, err + } + + err = ip.initKeys(secretManager) + if err != nil { + return results, err + } + + res, err := ip.getResult(secretManager) + if err != nil { + return results, err + } + + results[i] = res + } + + return results, nil +} + +func (ip *initParams) initKeys(secretsManager secrets.SecretsManager) error { + if ip.generatesNetwork { + if _, err := helper.InitNetworkingPrivateKey(secretsManager); err != nil { + return err + } + } + + if ip.generatesAccount { + if secretsManager.HasSecret(secrets.ValidatorKey) { + return fmt.Errorf("secrets '%s' has been already initialized", secrets.ValidatorKey) + } + + if secretsManager.HasSecret(secrets.ValidatorBLSKey) { + return fmt.Errorf("secrets '%s' has been already initialized", secrets.ValidatorBLSKey) + } + + return wallet.GenerateAccount().Save(secretsManager) + } + + return nil +} + +// getResult gets keys from secret manager and return result to display +func (ip *initParams) getResult(secretsManager secrets.SecretsManager) (command.CommandResult, error) { + var ( + res = &SecretsInitResult{} + err error + ) + + if ip.generatesAccount { + account, err := wallet.NewAccountFromSecret(secretsManager) + if err != nil { + return nil, err + } + + res.Address = types.Address(account.Ecdsa.Address()) + res.BLSPubkey = hex.EncodeToString(account.Bls.PublicKey().Marshal()) + + if ip.printPrivateKey { + pk, err := account.Ecdsa.MarshallPrivateKey() + if err != nil { + return nil, err + } + + res.PrivateKey = hex.EncodeToString(pk) + } + } + + if ip.generatesNetwork { + if res.NodeID, err = helper.LoadNodeID(secretsManager); err != nil { + return nil, err + } + } + + res.Insecure = ip.insecureLocalStore + + return res, nil +} + +func getSecretsManager(dataPath, configPath string, insecureLocalStore bool) (secrets.SecretsManager, error) { + if configPath != "" { + secretsConfig, readErr := secrets.ReadConfig(configPath) + if readErr != nil { + return nil, errInvalidConfig + } + + if !secrets.SupportedServiceManager(secretsConfig.Type) { + return nil, errUnsupportedType + } + + return helper.InitCloudSecretsManager(secretsConfig) + } + + //Storing secrets on a local file system should only be allowed with --insecure flag, + //to raise awareness that it should be only used in development/testing environments. + //Production setups should use one of the supported secrets managers + if !insecureLocalStore { + return nil, errSecureLocalStoreNotImplemented + } + + return helper.SetupLocalSecretsManager(dataPath) +} diff --git a/command/polybftsecrets/result.go b/command/polybftsecrets/result.go new file mode 100644 index 0000000000..7b29476eaf --- /dev/null +++ b/command/polybftsecrets/result.go @@ -0,0 +1,68 @@ +package polybftsecrets + +import ( + "bytes" + "fmt" + + "github.com/0xPolygon/polygon-edge/command" + + "github.com/0xPolygon/polygon-edge/command/helper" + "github.com/0xPolygon/polygon-edge/types" +) + +type Results []command.CommandResult + +func (r Results) GetOutput() string { + var buffer bytes.Buffer + + for _, result := range r { + buffer.WriteString(result.GetOutput()) + } + + return buffer.String() +} + +type SecretsInitResult struct { + Address types.Address `json:"address"` + BLSPubkey string `json:"bls_pubkey"` + NodeID string `json:"node_id"` + PrivateKey string `json:"private_key"` + Insecure bool `json:"insecure"` +} + +func (r *SecretsInitResult) GetOutput() string { + var buffer bytes.Buffer + + vals := make([]string, 0, 3) + + vals = append( + vals, + fmt.Sprintf("Public key (address)|%s", r.Address.String()), + ) + + if r.PrivateKey != "" { + vals = append( + vals, + fmt.Sprintf("Private key|%s", r.PrivateKey), + ) + } + + if r.BLSPubkey != "" { + vals = append( + vals, + fmt.Sprintf("BLS Public key|%s", r.BLSPubkey), + ) + } + + vals = append(vals, fmt.Sprintf("Node ID|%s", r.NodeID)) + + if r.Insecure { + buffer.WriteString("\n[WARNING: INSECURE LOCAL SECRETS - SHOULD NOT BE RUN IN PRODUCTION]\n") + } + + buffer.WriteString("\n[SECRETS INIT]\n") + buffer.WriteString(helper.FormatKV(vals)) + buffer.WriteString("\n") + + return buffer.String() +} diff --git a/command/polybftsecrets/secrets_init.go b/command/polybftsecrets/secrets_init.go new file mode 100644 index 0000000000..8665b78923 --- /dev/null +++ b/command/polybftsecrets/secrets_init.go @@ -0,0 +1,41 @@ +package polybftsecrets + +import ( + "github.com/spf13/cobra" + + "github.com/0xPolygon/polygon-edge/command" +) + +var basicParams = &initParams{} + +func GetCommand() *cobra.Command { + secretsInitCmd := &cobra.Command{ + Use: "polybft-secrets", + Short: "Initializes private keys for the Polygon Edge (Validator + Networking) " + + "to the specified Secrets Manager", + PreRunE: runPreRun, + Run: runCommand, + } + + basicParams.setFlags(secretsInitCmd) + + return secretsInitCmd +} + +func runPreRun(_ *cobra.Command, _ []string) error { + return basicParams.validateFlags() +} + +func runCommand(cmd *cobra.Command, _ []string) { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + results, err := basicParams.Execute() + if err != nil { + outputter.SetError(err) + + return + } + + outputter.SetCommandResult(results) +} diff --git a/command/results.go b/command/results.go new file mode 100644 index 0000000000..f5449234c6 --- /dev/null +++ b/command/results.go @@ -0,0 +1,16 @@ +package command + +import "bytes" + +// Results implements CommandResult interface by aggregating multiple commands outputs into one +type Results []CommandResult + +func (r Results) GetOutput() string { + var buffer bytes.Buffer + + for _, res := range r { + buffer.WriteString(res.GetOutput()) + } + + return buffer.String() +} diff --git a/command/root/root.go b/command/root/root.go index 2aca0cef2f..3b4802bc9f 100644 --- a/command/root/root.go +++ b/command/root/root.go @@ -4,6 +4,8 @@ import ( "fmt" "os" + "github.com/spf13/cobra" + "github.com/0xPolygon/polygon-edge/command/backup" "github.com/0xPolygon/polygon-edge/command/genesis" "github.com/0xPolygon/polygon-edge/command/helper" @@ -11,13 +13,16 @@ import ( "github.com/0xPolygon/polygon-edge/command/license" "github.com/0xPolygon/polygon-edge/command/monitor" "github.com/0xPolygon/polygon-edge/command/peers" + "github.com/0xPolygon/polygon-edge/command/polybft" + "github.com/0xPolygon/polygon-edge/command/polybftmanifest" + "github.com/0xPolygon/polygon-edge/command/polybftsecrets" + "github.com/0xPolygon/polygon-edge/command/rootchain" "github.com/0xPolygon/polygon-edge/command/secrets" "github.com/0xPolygon/polygon-edge/command/server" "github.com/0xPolygon/polygon-edge/command/status" "github.com/0xPolygon/polygon-edge/command/txpool" "github.com/0xPolygon/polygon-edge/command/version" "github.com/0xPolygon/polygon-edge/command/whitelist" - "github.com/spf13/cobra" ) type RootCommand struct { @@ -45,6 +50,7 @@ func (rc *RootCommand) registerSubCommands() { status.GetCommand(), secrets.GetCommand(), peers.GetCommand(), + rootchain.GetCommand(), monitor.GetCommand(), ibft.GetCommand(), backup.GetCommand(), @@ -52,6 +58,9 @@ func (rc *RootCommand) registerSubCommands() { server.GetCommand(), whitelist.GetCommand(), license.GetCommand(), + polybftsecrets.GetCommand(), + polybft.GetCommand(), + polybftmanifest.GetCommand(), ) } diff --git a/command/rootchain/README.md b/command/rootchain/README.md new file mode 100644 index 0000000000..674636f55c --- /dev/null +++ b/command/rootchain/README.md @@ -0,0 +1,30 @@ +# RootChain Helper + +## Start rootchain server + +This command starts `ethereum/client-go` container which is basically geth node, +and deploys the rootchain bridge and the checkpoint manager contracts. + +```bash +$ polygon-edge rootchain server +``` + +## Fund initialized accounts + +This command funds the initialized accounts via `polygon-edge secrets init ...` command. + +```bash +$ polygon-edge rootchain fund --data-dir data-dir- --num 2 +``` +Or +```bash +$ polygon-edge rootchain fund --data-dir data-dir-1 +``` + +## Emit event + +This command emits the event from the bridge side which invokes the wallets funding logic. + +```bash +$ polygon-edge rootchain emit --contract --wallets --amounts +``` \ No newline at end of file diff --git a/command/rootchain/emit/emit.go b/command/rootchain/emit/emit.go new file mode 100644 index 0000000000..cd7b6ed1a8 --- /dev/null +++ b/command/rootchain/emit/emit.go @@ -0,0 +1,187 @@ +package emit + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/abi" + "golang.org/x/sync/errgroup" + + "github.com/0xPolygon/polygon-edge/command" + "github.com/0xPolygon/polygon-edge/command/rootchain/helper" + "github.com/0xPolygon/polygon-edge/consensus/polybft" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" +) + +var ( + params emitParams + + contractsToParamTypes = map[string]string{ + contracts.NativeTokenContract.String(): "tuple(address,uint256)", + } + + syncStateAbiMethod = contractsapi.StateSender.Abi.Methods["syncState"] +) + +// GetCommand returns the rootchain emit command +func GetCommand() *cobra.Command { + rootchainEmitCmd := &cobra.Command{ + Use: "emit", + Short: "Emit an event from the bridge", + PreRunE: runPreRun, + Run: runCommand, + } + + setFlags(rootchainEmitCmd) + + return rootchainEmitCmd +} + +func setFlags(cmd *cobra.Command) { + cmd.Flags().StringVar( + ¶ms.manifestPath, + manifestPathFlag, + "./manifest.json", + "the manifest file path, which contains genesis metadata", + ) + + cmd.Flags().StringVar( + ¶ms.address, + contractFlag, + contracts.NativeTokenContract.String(), + "ERC20 bridge contract address", + ) + + cmd.Flags().StringSliceVar( + ¶ms.wallets, + walletsFlag, + nil, + "list of wallet addresses", + ) + + cmd.Flags().StringSliceVar( + ¶ms.amounts, + amountsFlag, + nil, + "list of amounts to fund wallets", + ) + + cmd.Flags().StringVar( + ¶ms.jsonRPCAddress, + jsonRPCFlag, + "http://127.0.0.1:8545", + "the JSON RPC rootchain IP address (e.g. http://127.0.0.1:8545)", + ) + + cmd.Flags().StringVar( + ¶ms.adminKey, + adminKeyFlag, + helper.DefaultPrivateKeyRaw, + "Hex encoded private key of the account which sends rootchain transactions", + ) +} + +func runPreRun(_ *cobra.Command, _ []string) error { + return params.validateFlags() +} + +func runCommand(cmd *cobra.Command, _ []string) { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + err := helper.InitRootchainAdminKey(params.adminKey) + if err != nil { + outputter.SetError(err) + + return + } + + manifest, err := polybft.LoadManifest(params.manifestPath) + if err != nil { + outputter.SetError(fmt.Errorf("failed to load manifest file from '%s': %w", params.manifestPath, err)) + + return + } + + paramsType, exists := contractsToParamTypes[params.address] + if !exists { + outputter.SetError(fmt.Errorf("no parameter types for given contract address: %v", params.address)) + + return + } + + txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPCAddress)) + if err != nil { + outputter.SetError(fmt.Errorf("could not create rootchain interactor: %w", err)) + + return + } + + g, ctx := errgroup.WithContext(cmd.Context()) + + for i := range params.wallets { + wallet := params.wallets[i] + amount := params.amounts[i] + + g.Go(func() error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + txn, err := createEmitTxn(manifest.RootchainConfig.StateSenderAddress, paramsType, wallet, amount) + if err != nil { + return fmt.Errorf("failed to create tx input: %w", err) + } + + if _, err = txRelayer.SendTransaction( + txn, + helper.GetRootchainAdminKey()); err != nil { + return fmt.Errorf("sending transaction to wallet: %s with amount: %s, failed with error: %w", wallet, amount, err) + } + + return nil + } + }) + } + + if err = g.Wait(); err != nil { + outputter.SetError(fmt.Errorf("sending transactions to rootchain failed: %w", err)) + + return + } + + outputter.SetCommandResult(&result{ + Address: params.address, + Wallets: params.wallets, + Amounts: params.amounts, + }) +} + +func createEmitTxn( + stateSenderAddr types.Address, + paramsType string, + parameters ...interface{}) (*ethgo.Transaction, error) { + var prms []interface{} + prms = append(prms, parameters...) + + wrapperInput, err := abi.MustNewType(paramsType).Encode(prms) + if err != nil { + return nil, fmt.Errorf("failed to encode parsed parameters: %w", err) + } + + sender := types.StringToAddress(params.address) + + input, err := syncStateAbiMethod.Encode([]interface{}{sender, wrapperInput}) + if err != nil { + return nil, fmt.Errorf("failed to encode provided parameters: %w", err) + } + + return ðgo.Transaction{ + To: (*ethgo.Address)(&stateSenderAddr), + Input: input, + }, nil +} diff --git a/command/rootchain/emit/params.go b/command/rootchain/emit/params.go new file mode 100644 index 0000000000..afc8b7f6fe --- /dev/null +++ b/command/rootchain/emit/params.go @@ -0,0 +1,51 @@ +package emit + +import ( + "errors" + "fmt" + "os" +) + +const ( + manifestPathFlag = "manifest" + contractFlag = "contract" + walletsFlag = "wallets" + amountsFlag = "amounts" + jsonRPCFlag = "json-rpc" + adminKeyFlag = "admin-key" +) + +var ( + errWalletsMissing = errors.New("wallet flag value is not provided") + errAmountsMissing = errors.New("amount flag value is not provided") + errInconsistentAccounts = errors.New("wallets and amounts must be provided in pairs") +) + +type emitParams struct { + manifestPath string + address string + wallets []string + amounts []string + jsonRPCAddress string + adminKey string +} + +func (ep *emitParams) validateFlags() error { + if _, err := os.Stat(ep.manifestPath); errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("provided manifest path '%s' doesn't exist", ep.manifestPath) + } + + if len(ep.wallets) == 0 { + return errWalletsMissing + } + + if len(ep.amounts) == 0 { + return errAmountsMissing + } + + if len(ep.wallets) != len(ep.amounts) { + return errInconsistentAccounts + } + + return nil +} diff --git a/command/rootchain/emit/result.go b/command/rootchain/emit/result.go new file mode 100644 index 0000000000..d255c92b94 --- /dev/null +++ b/command/rootchain/emit/result.go @@ -0,0 +1,30 @@ +package emit + +import ( + "bytes" + "fmt" + "strings" + + "github.com/0xPolygon/polygon-edge/command/helper" +) + +type result struct { + Address string `json:"address"` + Wallets []string `json:"wallets"` + Amounts []string `json:"amounts"` +} + +func (r *result) GetOutput() string { + var buffer bytes.Buffer + + vals := make([]string, 0, 3) + vals = append(vals, fmt.Sprintf("Contract (address)|%s", r.Address)) + vals = append(vals, fmt.Sprintf("Wallets|%s", strings.Join(r.Wallets, ", "))) + vals = append(vals, fmt.Sprintf("Amounts|%s", strings.Join(r.Amounts, ", "))) + + buffer.WriteString("\n[ROOTCHAIN EMIT]\n") + buffer.WriteString(helper.FormatKV(vals)) + buffer.WriteString("\n") + + return buffer.String() +} diff --git a/command/rootchain/fund/fund.go b/command/rootchain/fund/fund.go new file mode 100644 index 0000000000..44113dc59a --- /dev/null +++ b/command/rootchain/fund/fund.go @@ -0,0 +1,143 @@ +package fund + +import ( + "fmt" + "math/big" + + "github.com/spf13/cobra" + "github.com/umbracle/ethgo" + + "github.com/0xPolygon/polygon-edge/command" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" +) + +var ( + params fundParams + fundNumber int + jsonRPCAddress string +) + +// GetCommand returns the rootchain fund command +func GetCommand() *cobra.Command { + rootchainFundCmd := &cobra.Command{ + Use: "fund", + Short: "Fund funds all the genesis addresses", + PreRunE: runPreRun, + Run: runCommand, + } + + setFlags(rootchainFundCmd) + + return rootchainFundCmd +} + +func setFlags(cmd *cobra.Command) { + cmd.Flags().StringVar( + ¶ms.dataDir, + dataDirFlag, + "", + "the directory for the Polygon Edge data if the local FS is used", + ) + + cmd.Flags().StringVar( + ¶ms.configPath, + configFlag, + "", + "the path to the SecretsManager config file, "+ + "if omitted, the local FS secrets manager is used", + ) + + cmd.Flags().IntVar( + &fundNumber, + numFlag, + 1, + "the flag indicating the number of accounts to be funded", + ) + + cmd.Flags().StringVar( + &jsonRPCAddress, + jsonRPCFlag, + "http://127.0.0.1:8545", + "the JSON RPC rootchain IP address (e.g. http://127.0.0.1:8545)", + ) + + // Don't accept data-dir and config flags because they are related to different secrets managers. + // data-dir is about the local FS as secrets storage, config is about remote secrets manager. + cmd.MarkFlagsMutuallyExclusive(dataDirFlag, configFlag) + + // num flag should be used with data-dir flag only so it should not be used with config flag. + cmd.MarkFlagsMutuallyExclusive(numFlag, configFlag) +} + +func runPreRun(_ *cobra.Command, _ []string) error { + return params.validateFlags() +} + +func runCommand(cmd *cobra.Command, _ []string) { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + paramsList := getParamsList() + resList := make(command.Results, len(paramsList)) + + txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(jsonRPCAddress)) + if err != nil { + outputter.SetError(fmt.Errorf("failed to initialize tx relayer: %w", err)) + + return + } + + for i, params := range paramsList { + if err := params.initSecretsManager(); err != nil { + outputter.SetError(err) + + return + } + + validatorAcc, err := params.getValidatorAccount() + if err != nil { + outputter.SetError(err) + + return + } + + fundAddr := ethgo.Address(validatorAcc) + txn := ðgo.Transaction{ + To: &fundAddr, + Value: big.NewInt(1000000000000000000), + } + + receipt, err := txRelayer.SendTransactionLocal(txn) + if err != nil { + outputter.SetError(err) + + return + } + + resList[i] = &result{ + ValidatorAddr: validatorAcc, + TxHash: types.Hash(receipt.TransactionHash), + } + } + + outputter.SetCommandResult(resList) +} + +// getParamsList creates a list of initParams with num elements. +// This function basically copies the given initParams but updating dataDir by applying an index. +func getParamsList() []fundParams { + if fundNumber == 1 { + return []fundParams{params} + } + + paramsList := make([]fundParams, fundNumber) + for i := 1; i <= fundNumber; i++ { + paramsList[i-1] = fundParams{ + dataDir: fmt.Sprintf("%s%d", params.dataDir, i), + configPath: params.configPath, + } + } + + return paramsList +} diff --git a/command/rootchain/fund/params.go b/command/rootchain/fund/params.go new file mode 100644 index 0000000000..8489cf7f11 --- /dev/null +++ b/command/rootchain/fund/params.go @@ -0,0 +1,87 @@ +package fund + +import ( + "errors" + + "github.com/0xPolygon/polygon-edge/secrets" + "github.com/0xPolygon/polygon-edge/secrets/helper" + "github.com/0xPolygon/polygon-edge/types" +) + +const ( + dataDirFlag = "data-dir" + configFlag = "config" + numFlag = "num" + jsonRPCFlag = "json-rpc" +) + +var ( + errInvalidConfig = errors.New("invalid secrets configuration") + errInvalidParams = errors.New("no config file or data directory passed in") + errUnsupportedType = errors.New("unsupported secrets manager") +) + +type fundParams struct { + dataDir string + configPath string + + secretsManager secrets.SecretsManager + secretsConfig *secrets.SecretsManagerConfig +} + +func (fp *fundParams) validateFlags() error { + if fp.dataDir == "" && fp.configPath == "" { + return errInvalidParams + } + + return nil +} + +func (fp *fundParams) hasConfigPath() bool { + return fp.configPath != "" +} + +func (fp *fundParams) initSecretsManager() error { + var err error + if fp.hasConfigPath() { + if err = fp.parseConfig(); err != nil { + return err + } + + fp.secretsManager, err = helper.InitCloudSecretsManager(fp.secretsConfig) + + return err + } + + return fp.initLocalSecretsManager() +} + +func (fp *fundParams) parseConfig() error { + secretsConfig, readErr := secrets.ReadConfig(fp.configPath) + if readErr != nil { + return errInvalidConfig + } + + if !secrets.SupportedServiceManager(secretsConfig.Type) { + return errUnsupportedType + } + + fp.secretsConfig = secretsConfig + + return nil +} + +func (fp *fundParams) initLocalSecretsManager() error { + local, err := helper.SetupLocalSecretsManager(fp.dataDir) + if err != nil { + return err + } + + fp.secretsManager = local + + return nil +} + +func (fp *fundParams) getValidatorAccount() (types.Address, error) { + return helper.LoadValidatorAddress(fp.secretsManager) +} diff --git a/command/rootchain/fund/result.go b/command/rootchain/fund/result.go new file mode 100644 index 0000000000..608cf64907 --- /dev/null +++ b/command/rootchain/fund/result.go @@ -0,0 +1,28 @@ +package fund + +import ( + "bytes" + "fmt" + + "github.com/0xPolygon/polygon-edge/command/helper" + "github.com/0xPolygon/polygon-edge/types" +) + +type result struct { + ValidatorAddr types.Address `json:"address"` + TxHash types.Hash `json:"tx_hash"` +} + +func (r *result) GetOutput() string { + var buffer bytes.Buffer + + vals := make([]string, 0, 2) + vals = append(vals, fmt.Sprintf("Validator (address)|%s", r.ValidatorAddr)) + vals = append(vals, fmt.Sprintf("Transaction (hash)|%s", r.TxHash)) + + buffer.WriteString("\n[ROOTCHAIN FUND]\n") + buffer.WriteString(helper.FormatKV(vals)) + buffer.WriteString("\n") + + return buffer.String() +} diff --git a/command/rootchain/helper/metadata.go b/command/rootchain/helper/metadata.go new file mode 100644 index 0000000000..85154ed38a --- /dev/null +++ b/command/rootchain/helper/metadata.go @@ -0,0 +1,93 @@ +package helper + +import ( + "context" + "encoding/hex" + "errors" + "fmt" + + dockertypes "github.com/docker/docker/api/types" + "github.com/docker/docker/client" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/wallet" +) + +const DefaultPrivateKeyRaw = "aa75e9a7d427efc732f8e4f1a5b7646adcc61fd5bae40f80d13c8419c9f43d6d" + +var ( + ErrRootchainNotFound = errors.New("rootchain not found") + ErrRootchainPortBind = errors.New("port 8545 is not bind with localhost") + + // rootchainAdminKey is a private key of account which is rootchain administrator + // namely it represents account which deploys rootchain smart contracts + rootchainAdminKey *wallet.Key +) + +// InitRootchainAdminKey initializes a private key instance from provided hex encoded private key +func InitRootchainAdminKey(rawKey string) error { + privateKeyRaw := DefaultPrivateKeyRaw + if rawKey != "" { + privateKeyRaw = rawKey + } + + dec, err := hex.DecodeString(privateKeyRaw) + if err != nil { + return fmt.Errorf("failed to decode private key string '%s': %w", privateKeyRaw, err) + } + + rootchainAdminKey, err = wallet.NewWalletFromPrivKey(dec) + if err != nil { + return fmt.Errorf("failed to initialize key from provided private key '%s': %w", privateKeyRaw, err) + } + + return nil +} + +// GetRootchainAdminKey returns rootchain admin private key +func GetRootchainAdminKey() ethgo.Key { + return rootchainAdminKey +} + +func GetRootchainID() (string, error) { + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return "", fmt.Errorf("rootchain id error: %w", err) + } + + containers, err := cli.ContainerList(context.Background(), dockertypes.ContainerListOptions{}) + if err != nil { + return "", fmt.Errorf("rootchain id error: %w", err) + } + + for _, c := range containers { + if c.Labels["edge-type"] == "rootchain" { + return c.ID, nil + } + } + + return "", ErrRootchainNotFound +} + +func ReadRootchainIP() (string, error) { + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return "", fmt.Errorf("rootchain id error: %w", err) + } + + contID, err := GetRootchainID() + if err != nil { + return "", err + } + + inspect, err := cli.ContainerInspect(context.Background(), contID) + if err != nil { + return "", fmt.Errorf("rootchain ip error: %w", err) + } + + ports, ok := inspect.HostConfig.PortBindings["8545/tcp"] + if !ok || len(ports) == 0 { + return "", ErrRootchainPortBind + } + + return fmt.Sprintf("http://%s:%s", ports[0].HostIP, ports[0].HostPort), nil +} diff --git a/command/rootchain/initcontracts/init_contracts.go b/command/rootchain/initcontracts/init_contracts.go new file mode 100644 index 0000000000..7ab77a1506 --- /dev/null +++ b/command/rootchain/initcontracts/init_contracts.go @@ -0,0 +1,351 @@ +package initcontracts + +import ( + "bytes" + "errors" + "fmt" + "math/big" + "sort" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi/artifact" + + "github.com/spf13/cobra" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/jsonrpc" + + "github.com/0xPolygon/polygon-edge/command" + "github.com/0xPolygon/polygon-edge/command/rootchain/helper" + "github.com/0xPolygon/polygon-edge/consensus/polybft" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" +) + +const ( + contractsDeploymentTitle = "[ROOTCHAIN - CONTRACTS DEPLOYMENT]" + + stateSenderName = "StateSender" + checkpointManagerName = "CheckpointManager" + blsName = "BLS" + bn256G2Name = "BN256G2" + exitHelperName = "ExitHelper" +) + +var ( + params initContractsParams + + // metadataPopulatorMap maps rootchain contract names to callback + // which populates appropriate field in the RootchainMetadata + metadataPopulatorMap = map[string]func(*polybft.RootchainConfig, types.Address){ + stateSenderName: func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { + rootchainConfig.StateSenderAddress = addr + }, + checkpointManagerName: func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { + rootchainConfig.CheckpointManagerAddress = addr + }, + blsName: func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { + rootchainConfig.BLSAddress = addr + }, + bn256G2Name: func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { + rootchainConfig.BN256G2Address = addr + }, + exitHelperName: func(rootchainConfig *polybft.RootchainConfig, addr types.Address) { + rootchainConfig.ExitHelperAddress = addr + }, + } +) + +// GetCommand returns the rootchain emit command +func GetCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "init-contracts", + Short: "Deploys and initializes required smart contracts on the rootchain", + PreRunE: runPreRun, + Run: runCommand, + } + + setFlags(cmd) + + return cmd +} + +func setFlags(cmd *cobra.Command) { + cmd.Flags().StringVar( + ¶ms.contractsPath, + contractsPathFlag, + contracts.ContractsRootFolder, + "Root directory path containing POS smart contracts", + ) + + cmd.Flags().StringVar( + ¶ms.manifestPath, + manifestPathFlag, + defaultManifestPath, + "Manifest file path, which contains metadata", + ) + + cmd.Flags().StringVar( + ¶ms.adminKey, + adminKeyFlag, + helper.DefaultPrivateKeyRaw, + "Hex encoded private key of the account which deploys rootchain contracts", + ) + + cmd.Flags().StringVar( + ¶ms.jsonRPCAddress, + jsonRPCFlag, + txrelayer.DefaultRPCAddress, + "the JSON RPC rootchain IP address (e.g. "+txrelayer.DefaultRPCAddress+")", + ) +} + +func runPreRun(cmd *cobra.Command, _ []string) error { + return params.validateFlags() +} + +func runCommand(cmd *cobra.Command, _ []string) { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + outputter.WriteCommandResult(&messageResult{ + Message: fmt.Sprintf("%s started...", contractsDeploymentTitle), + }) + + client, err := jsonrpc.NewClient(params.jsonRPCAddress) + if err != nil { + outputter.SetError(fmt.Errorf("failed to initialize JSON RPC client for provided IP address: %s: %w", + params.jsonRPCAddress, err)) + + return + } + + manifest, err := polybft.LoadManifest(params.manifestPath) + if err != nil { + outputter.SetError(fmt.Errorf("failed to read manifest: %w", err)) + + return + } + + if manifest.RootchainConfig != nil { + code, err := client.Eth().GetCode(ethgo.Address(manifest.RootchainConfig.StateSenderAddress), ethgo.Latest) + if err != nil { + outputter.SetError(fmt.Errorf("failed to check if rootchain contracts are deployed: %w", err)) + + return + } else if code != "0x" { + outputter.SetCommandResult(&messageResult{ + Message: fmt.Sprintf("%s contracts are already deployed. Aborting.", contractsDeploymentTitle), + }) + + return + } + } + + if err := helper.InitRootchainAdminKey(params.adminKey); err != nil { + outputter.SetError(err) + + return + } + + if err := deployContracts(outputter, client, manifest); err != nil { + outputter.SetError(fmt.Errorf("failed to deploy rootchain contracts: %w", err)) + + return + } + + outputter.SetCommandResult(&messageResult{ + Message: fmt.Sprintf("%s finished. All contracts are successfully deployed and initialized.", + contractsDeploymentTitle), + }) +} + +func deployContracts(outputter command.OutputFormatter, client *jsonrpc.Client, manifest *polybft.Manifest) error { + // if the bridge contract is not created, we have to deploy all the contracts + txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithClient(client)) + if err != nil { + return fmt.Errorf("failed to initialize tx relayer: %w", err) + } + + rootchainAdminKey := helper.GetRootchainAdminKey() + // if admin key is equal to the test private key, then we assume we are working in dev mode + // and therefore need to fund that account + if params.adminKey == helper.DefaultPrivateKeyRaw { + // fund account + rootchainAdminAddr := rootchainAdminKey.Address() + txn := ðgo.Transaction{To: &rootchainAdminAddr, Value: big.NewInt(1000000000000000000)} + _, err = txRelayer.SendTransactionLocal(txn) + + if err != nil { + return err + } + } + + deployContracts := []struct { + name string + artifact *artifact.Artifact + }{ + { + name: "StateSender", + artifact: contractsapi.StateSender, + }, + { + name: "CheckpointManager", + artifact: contractsapi.CheckpointManager, + }, + { + name: "BLS", + artifact: contractsapi.BLS, + }, + { + name: "BN256G2", + artifact: contractsapi.BLS256, + }, + { + name: "ExitHelper", + artifact: contractsapi.ExitHelper, + }, + } + + rootchainConfig := &polybft.RootchainConfig{} + manifest.RootchainConfig = rootchainConfig + rootchainConfig.AdminAddress = types.Address(rootchainAdminKey.Address()) + + for _, contract := range deployContracts { + txn := ðgo.Transaction{ + To: nil, // contract deployment + Input: contract.artifact.Bytecode, + } + + receipt, err := txRelayer.SendTransaction(txn, rootchainAdminKey) + if err != nil { + return err + } + + contractAddr := types.Address(receipt.ContractAddress) + + populatorFn, ok := metadataPopulatorMap[contract.name] + if !ok { + return fmt.Errorf("rootchain metadata populator not registered for contract '%s'", contract.name) + } + + populatorFn(manifest.RootchainConfig, contractAddr) + + outputter.WriteCommandResult(newDeployContractsResult(contract.name, contractAddr, receipt.TransactionHash)) + } + + if err := manifest.Save(params.manifestPath); err != nil { + return fmt.Errorf("failed to save manifest data: %w", err) + } + + if err := initializeCheckpointManager(txRelayer, rootchainAdminKey, manifest); err != nil { + return err + } + + outputter.WriteCommandResult(&messageResult{ + Message: fmt.Sprintf("%s CheckpointManager contract is initialized", contractsDeploymentTitle), + }) + + if err := initializeExitHelper(txRelayer, rootchainConfig); err != nil { + return err + } + + outputter.WriteCommandResult(&messageResult{ + Message: fmt.Sprintf("%s ExitHelper contract is initialized", contractsDeploymentTitle), + }) + + return nil +} + +// initializeCheckpointManager invokes initialize function on CheckpointManager smart contract +func initializeCheckpointManager( + txRelayer txrelayer.TxRelayer, + rootchainAdminKey ethgo.Key, + manifest *polybft.Manifest) error { + validatorSet, err := validatorSetToABISlice(manifest.GenesisValidators) + if err != nil { + return fmt.Errorf("failed to convert validators to map: %w", err) + } + + initialize := contractsapi.InitializeCheckpointManagerFunction{ + NewBls: manifest.RootchainConfig.BLSAddress, + NewBn256G2: manifest.RootchainConfig.BN256G2Address, + NewDomain: types.BytesToHash(bls.GetDomain()), + NewValidatorSet: validatorSet, + } + + initCheckpointInput, err := initialize.EncodeAbi() + if err != nil { + return fmt.Errorf("failed to encode parameters for CheckpointManager.initialize. error: %w", err) + } + + checkpointManagerAddress := ethgo.Address(manifest.RootchainConfig.CheckpointManagerAddress) + txn := ðgo.Transaction{ + To: &checkpointManagerAddress, + Input: initCheckpointInput, + } + + receipt, err := txRelayer.SendTransaction(txn, rootchainAdminKey) + if err != nil { + return fmt.Errorf("failed to send transaction to CheckpointManager. error: %w", err) + } + + if receipt.Status != uint64(types.ReceiptSuccess) { + return errors.New("failed to initialize CheckpointManager") + } + + return nil +} + +func initializeExitHelper(txRelayer txrelayer.TxRelayer, rootchainConfig *polybft.RootchainConfig) error { + input, err := contractsapi.ExitHelper.Abi.GetMethod("initialize"). + Encode([]interface{}{rootchainConfig.CheckpointManagerAddress}) + if err != nil { + return fmt.Errorf("failed to encode parameters for ExitHelper.initialize. error: %w", err) + } + + exitHelperAddr := ethgo.Address(rootchainConfig.ExitHelperAddress) + txn := ðgo.Transaction{ + To: &exitHelperAddr, + Input: input, + } + + receipt, err := txRelayer.SendTransaction(txn, helper.GetRootchainAdminKey()) + if err != nil { + return fmt.Errorf("failed to send transaction to ExitHelper. error: %w", err) + } + + if receipt.Status != uint64(types.ReceiptSuccess) { + return errors.New("failed to initialize ExitHelper contract") + } + + return nil +} + +// validatorSetToABISlice converts given validators to generic map +// which is used for ABI encoding validator set being sent to the rootchain contract +func validatorSetToABISlice(validators []*polybft.Validator) ([]*contractsapi.Validator, error) { + genesisValidators := make([]*polybft.Validator, len(validators)) + copy(genesisValidators, validators) + sort.Slice(genesisValidators, func(i, j int) bool { + return bytes.Compare(genesisValidators[i].Address.Bytes(), genesisValidators[j].Address.Bytes()) < 0 + }) + + accSet := make(polybft.AccountSet, len(genesisValidators)) + + for i, validatorInfo := range genesisValidators { + blsKey, err := validatorInfo.UnmarshalBLSPublicKey() + if err != nil { + return nil, err + } + + accSet[i] = &polybft.ValidatorMetadata{ + Address: validatorInfo.Address, + BlsKey: blsKey, + VotingPower: new(big.Int).Set(validatorInfo.Balance), + } + } + + return accSet.ToAPIBinding(), nil +} diff --git a/command/rootchain/initcontracts/params.go b/command/rootchain/initcontracts/params.go new file mode 100644 index 0000000000..cbdbf8ce3e --- /dev/null +++ b/command/rootchain/initcontracts/params.go @@ -0,0 +1,35 @@ +package initcontracts + +import ( + "errors" + "fmt" + "os" +) + +const ( + contractsPathFlag = "path" + manifestPathFlag = "manifest" + jsonRPCFlag = "json-rpc" + adminKeyFlag = "admin-key" + + defaultManifestPath = "./manifest.json" +) + +type initContractsParams struct { + contractsPath string + manifestPath string + adminKey string + jsonRPCAddress string +} + +func (ip *initContractsParams) validateFlags() error { + if _, err := os.Stat(ip.contractsPath); errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("provided smart contracts directory '%s' doesn't exist", ip.contractsPath) + } + + if _, err := os.Stat(ip.manifestPath); errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("provided manifest path '%s' doesn't exist", ip.manifestPath) + } + + return nil +} diff --git a/command/rootchain/initcontracts/result.go b/command/rootchain/initcontracts/result.go new file mode 100644 index 0000000000..b268b571dd --- /dev/null +++ b/command/rootchain/initcontracts/result.go @@ -0,0 +1,53 @@ +package initcontracts + +import ( + "bytes" + "fmt" + + "github.com/0xPolygon/polygon-edge/command/helper" + "github.com/0xPolygon/polygon-edge/types" + "github.com/umbracle/ethgo" +) + +type deployContractResult struct { + Name string `json:"name"` + Address types.Address `json:"address"` + Hash types.Hash `json:"hash"` +} + +func newDeployContractsResult(name string, address types.Address, hash ethgo.Hash) *deployContractResult { + return &deployContractResult{ + Name: name, + Address: address, + Hash: types.BytesToHash(hash.Bytes()), + } +} + +func (r deployContractResult) GetOutput() string { + var buffer bytes.Buffer + + buffer.WriteString("\n[ROOTCHAIN - DEPLOY CONTRACT]\n") + + vals := make([]string, 0, 3) + vals = append(vals, fmt.Sprintf("Name|%s", r.Name)) + vals = append(vals, fmt.Sprintf("Contract (address)|%s", r.Address)) + vals = append(vals, fmt.Sprintf("Transaction (hash)|%s", r.Hash)) + + buffer.WriteString(helper.FormatKV(vals)) + buffer.WriteString("\n") + + return buffer.String() +} + +type messageResult struct { + Message string `json:"message"` +} + +func (r messageResult) GetOutput() string { + var buffer bytes.Buffer + + buffer.WriteString(r.Message) + buffer.WriteString("\n") + + return buffer.String() +} diff --git a/command/rootchain/rootchain.go b/command/rootchain/rootchain.go new file mode 100644 index 0000000000..4c548a85cd --- /dev/null +++ b/command/rootchain/rootchain.go @@ -0,0 +1,38 @@ +package rootchain + +import ( + "github.com/spf13/cobra" + + "github.com/0xPolygon/polygon-edge/command/helper" + "github.com/0xPolygon/polygon-edge/command/rootchain/emit" + "github.com/0xPolygon/polygon-edge/command/rootchain/fund" + "github.com/0xPolygon/polygon-edge/command/rootchain/initcontracts" + "github.com/0xPolygon/polygon-edge/command/rootchain/server" +) + +// GetCommand creates "rootchain" helper command +func GetCommand() *cobra.Command { + rootchainCmd := &cobra.Command{ + Use: "rootchain", + Short: "Top level RootChain helper command.", + } + + helper.RegisterGRPCAddressFlag(rootchainCmd) + + registerSubcommands(rootchainCmd) + + return rootchainCmd +} + +func registerSubcommands(baseCmd *cobra.Command) { + baseCmd.AddCommand( + // rootchain emit + emit.GetCommand(), + // rootchain fund + fund.GetCommand(), + // rootchain server + server.GetCommand(), + // init-contracts + initcontracts.GetCommand(), + ) +} diff --git a/command/rootchain/server/params.go b/command/rootchain/server/params.go new file mode 100644 index 0000000000..5a20d7baac --- /dev/null +++ b/command/rootchain/server/params.go @@ -0,0 +1,11 @@ +package server + +const ( + dataDirFlag = "data-dir" + noConsole = "no-console" +) + +type serverParams struct { + dataDir string + noConsole bool +} diff --git a/command/rootchain/server/result.go b/command/rootchain/server/result.go new file mode 100644 index 0000000000..b12e6dec00 --- /dev/null +++ b/command/rootchain/server/result.go @@ -0,0 +1,41 @@ +package server + +import ( + "bytes" + "fmt" + + "github.com/docker/docker/api/types/container" + + "github.com/0xPolygon/polygon-edge/command/helper" +) + +type containerStopResult struct { + Status int64 `json:"status"` + Err string `json:"err"` +} + +func newContainerStopResult(status container.ContainerWaitOKBody) *containerStopResult { + var errMsg string + if status.Error != nil { + errMsg = status.Error.Message + } + + return &containerStopResult{ + Status: status.StatusCode, + Err: errMsg, + } +} + +func (r containerStopResult) GetOutput() string { + var buffer bytes.Buffer + + vals := make([]string, 0, 2) + vals = append(vals, fmt.Sprintf("Status|%d", r.Status)) + vals = append(vals, fmt.Sprintf("Error|%s", r.Err)) + + buffer.WriteString("\n[ROOTCHAIN SERVER - STOP]\n") + buffer.WriteString(helper.FormatKV(vals)) + buffer.WriteString("\n") + + return buffer.String() +} diff --git a/command/rootchain/server/server.go b/command/rootchain/server/server.go new file mode 100644 index 0000000000..06813fbe9c --- /dev/null +++ b/command/rootchain/server/server.go @@ -0,0 +1,298 @@ +package server + +import ( + "context" + "errors" + "fmt" + "io" + "log" + "net/http" + "os" + "os/signal" + "path/filepath" + "strings" + "syscall" + "time" + + dockertypes "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + dockerclient "github.com/docker/docker/client" + "github.com/docker/docker/pkg/stdcopy" + "github.com/docker/go-connections/nat" + "github.com/spf13/cobra" + + "github.com/0xPolygon/polygon-edge/command" + "github.com/0xPolygon/polygon-edge/command/rootchain/helper" + "github.com/0xPolygon/polygon-edge/helper/common" +) + +const ( + gethConsoleImage = "ghcr.io/0xpolygon/go-ethereum-console:latest" + gethImage = "ethereum/client-go:v1.9.25" + + defaultHostIP = "127.0.0.1" + defaultHostPort = "8545" +) + +var ( + params serverParams + dockerClient *dockerclient.Client + dockerContainerID string +) + +// GetCommand returns the rootchain server command +func GetCommand() *cobra.Command { + rootchainServerCmd := &cobra.Command{ + Use: "server", + Short: "Start the rootchain command", + PreRunE: runPreRun, + Run: runCommand, + } + + setFlags(rootchainServerCmd) + + return rootchainServerCmd +} + +func setFlags(cmd *cobra.Command) { + cmd.Flags().StringVar( + ¶ms.dataDir, + dataDirFlag, + "test-rootchain", + "target directory for the chain", + ) + + cmd.Flags().BoolVar( + ¶ms.noConsole, + noConsole, + false, + "use the official geth image instead of the console fork", + ) +} + +func runPreRun(_ *cobra.Command, _ []string) error { + return nil +} + +func runCommand(cmd *cobra.Command, _ []string) { + ctx := cmd.Context() + + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + closeCh := make(chan struct{}) + + // Check if the client is already running + if cid, err := helper.GetRootchainID(); !errors.Is(err, helper.ErrRootchainNotFound) { + if err != nil { + outputter.SetError(err) + } else if cid != "" { + outputter.SetError(fmt.Errorf("rootchain already running: %s", cid)) + } + + return + } + + // Start the client + if err := runRootchain(ctx, outputter, closeCh); err != nil { + outputter.SetError(fmt.Errorf("failed to run rootchain: %w", err)) + + return + } + + // Ping geth server to make sure everything is up and running + if err := PingServer(closeCh); err != nil { + close(closeCh) + + if ip, err := helper.ReadRootchainIP(); err != nil { + outputter.SetError(fmt.Errorf("failed to ping rootchain server: %w", err)) + } else { + outputter.SetError(fmt.Errorf("failed to ping rootchain server at address %s: %w", ip, err)) + } + + return + } + + // Gather the logs + go func() { + if err := gatherLogs(ctx, outputter); err != nil { + outputter.SetError(fmt.Errorf("failed to gether logs: %w", err)) + + return + } + }() + + if err := handleSignals(ctx, closeCh); err != nil { + outputter.SetError(fmt.Errorf("failed to handle signals: %w", err)) + } +} + +func runRootchain(ctx context.Context, outputter command.OutputFormatter, closeCh chan struct{}) error { + var err error + if dockerClient, err = dockerclient.NewClientWithOpts(dockerclient.FromEnv); err != nil { + return err + } + + // target directory for the chain + if err = common.CreateDirSafe(params.dataDir, 0700); err != nil { + return err + } + + image := gethConsoleImage + if params.noConsole { + image = gethImage + } + + // try to pull the image + reader, err := dockerClient.ImagePull(ctx, image, dockertypes.ImagePullOptions{}) + if err != nil { + return err + } + defer reader.Close() + + if _, err = io.Copy(outputter, reader); err != nil { + return fmt.Errorf("cannot copy: %w", err) + } + + // create the client + args := []string{"--dev"} + + // add period of 2 seconds + args = append(args, "--dev.period", "2") + + // add data dir + args = append(args, "--datadir", "/eth1data") + + // add ipcpath + args = append(args, "--ipcpath", "/eth1data/geth.ipc") + + // enable rpc + args = append(args, "--http", "--http.addr", "0.0.0.0", "--http.api", "eth,net,web3,debug") + + // enable ws + args = append(args, "--ws", "--ws.addr", "0.0.0.0") + + config := &container.Config{ + Image: image, + Cmd: args, + Labels: map[string]string{ + "edge-type": "rootchain", + }, + } + + mountDir := params.dataDir + + // we need to use the full path + if !strings.HasPrefix(params.dataDir, "/") { + // if the path is not absolute, assume we want to create it locally + // in current folder + pwdDir, err := os.Getwd() + if err != nil { + log.Fatal(err) + } else { + mountDir = filepath.Join(pwdDir, params.dataDir) + } + } + + port := nat.Port(fmt.Sprintf("%s/tcp", defaultHostPort)) + hostConfig := &container.HostConfig{ + Binds: []string{ + mountDir + ":/eth1data", + }, + PortBindings: nat.PortMap{ + port: []nat.PortBinding{ + { + HostIP: defaultHostIP, + HostPort: defaultHostPort, + }, + }, + }, + } + + resp, err := dockerClient.ContainerCreate(ctx, config, hostConfig, nil, nil, "") + if err != nil { + return err + } + + // start the client + if err = dockerClient.ContainerStart(ctx, resp.ID, dockertypes.ContainerStartOptions{}); err != nil { + return err + } + + dockerContainerID = resp.ID + + // wait for it to finish + go func() { + statusCh, errCh := dockerClient.ContainerWait(ctx, dockerContainerID, container.WaitConditionNotRunning) + select { + case err = <-errCh: + outputter.SetError(err) + case status := <-statusCh: + outputter.SetCommandResult(newContainerStopResult(status)) + } + close(closeCh) + }() + + return nil +} + +func gatherLogs(ctx context.Context, outputter command.OutputFormatter) error { + opts := dockertypes.ContainerLogsOptions{ + ShowStderr: true, + ShowStdout: true, + Follow: true, + } + + out, err := dockerClient.ContainerLogs(ctx, dockerContainerID, opts) + if err != nil { + return fmt.Errorf("failed to retrieve container logs: %w", err) + } + + if _, err = stdcopy.StdCopy(outputter, outputter, out); err != nil { + return fmt.Errorf("failed to write container logs to the stdout: %w", err) + } + + return nil +} + +func PingServer(closeCh <-chan struct{}) error { + httpTimer := time.NewTimer(30 * time.Second) + httpClient := http.Client{ + Timeout: 5 * time.Second, + } + + for { + select { + case <-time.After(500 * time.Millisecond): + resp, err := httpClient.Post(fmt.Sprintf("http://%s:%s", defaultHostIP, defaultHostPort), "application/json", nil) + if err == nil { + return resp.Body.Close() + } + case <-httpTimer.C: + return fmt.Errorf("timeout to start http") + case <-closeCh: + return fmt.Errorf("closed before connecting with http. Is there any other process running and using rootchain dir?") + } + } +} + +func handleSignals(ctx context.Context, closeCh <-chan struct{}) error { + signalCh := make(chan os.Signal, 4) + signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) + + stop := true + select { + case <-signalCh: + case <-closeCh: + stop = false + } + + // close the container if possible + if stop { + if err := dockerClient.ContainerStop(ctx, dockerContainerID, nil); err != nil { + return fmt.Errorf("failed to stop container: %w", err) + } + } + + return nil +} diff --git a/command/secrets/init/result.go b/command/secrets/init/result.go index 2ab1ec8e6a..9c281e31f5 100644 --- a/command/secrets/init/result.go +++ b/command/secrets/init/result.go @@ -4,24 +4,10 @@ import ( "bytes" "fmt" - "github.com/0xPolygon/polygon-edge/command" - "github.com/0xPolygon/polygon-edge/command/helper" "github.com/0xPolygon/polygon-edge/types" ) -type Results []command.CommandResult - -func (r Results) GetOutput() string { - var buffer bytes.Buffer - - for _, result := range r { - buffer.WriteString(result.GetOutput()) - } - - return buffer.String() -} - type SecretsInitResult struct { Address types.Address `json:"address"` BLSPubkey string `json:"bls_pubkey"` diff --git a/command/secrets/init/secrets_init.go b/command/secrets/init/secrets_init.go index 52b37c64f9..67c04fd99f 100644 --- a/command/secrets/init/secrets_init.go +++ b/command/secrets/init/secrets_init.go @@ -105,8 +105,8 @@ func runCommand(cmd *cobra.Command, _ []string) { outputter := command.InitializeOutputter(cmd) defer outputter.WriteOutput() - paramsList := newParamsList(basicParams, initNumber) - results := make(Results, len(paramsList)) + paramsList := getParamsList() + results := make(command.Results, len(paramsList)) for i, params := range paramsList { if err := params.initSecrets(); err != nil { @@ -128,21 +128,22 @@ func runCommand(cmd *cobra.Command, _ []string) { outputter.SetCommandResult(results) } -// newParamsList creates a list of initParams with num elements. +// getParamsList creates a list of initParams with num elements. // This function basically copies the given initParams but updating dataDir by applying an index. -func newParamsList(params initParams, num int) []initParams { - if num == 1 { - return []initParams{params} +func getParamsList() []initParams { + if initNumber == 1 { + return []initParams{basicParams} } - paramsList := make([]initParams, num) - for i := 1; i <= num; i++ { + paramsList := make([]initParams, initNumber) + for i := 1; i <= initNumber; i++ { paramsList[i-1] = initParams{ - dataDir: fmt.Sprintf("%s%d", params.dataDir, i), - generatesECDSA: params.generatesECDSA, - generatesBLS: params.generatesBLS, - generatesNetwork: params.generatesNetwork, - insecureLocalStore: params.insecureLocalStore, + dataDir: fmt.Sprintf("%s%d", basicParams.dataDir, i), + configPath: basicParams.configPath, + generatesECDSA: basicParams.generatesECDSA, + generatesBLS: basicParams.generatesBLS, + generatesNetwork: basicParams.generatesNetwork, + insecureLocalStore: basicParams.insecureLocalStore, } } diff --git a/command/server/config/config.go b/command/server/config/config.go index 71a970f3d3..aca6497347 100644 --- a/command/server/config/config.go +++ b/command/server/config/config.go @@ -31,6 +31,7 @@ type Config struct { JSONRPCBatchRequestLimit uint64 `json:"json_rpc_batch_request_limit" yaml:"json_rpc_batch_request_limit"` JSONRPCBlockRangeLimit uint64 `json:"json_rpc_block_range_limit" yaml:"json_rpc_block_range_limit"` JSONLogFormat bool `json:"json_log_format" yaml:"json_log_format"` + Relayer bool `json:"relayer" yaml:"relayer"` } // Telemetry holds the config details for metric services. @@ -111,6 +112,7 @@ func DefaultConfig() *Config { LogFilePath: "", JSONRPCBatchRequestLimit: DefaultJSONRPCBatchRequestLimit, JSONRPCBlockRangeLimit: DefaultJSONRPCBlockRangeLimit, + Relayer: false, } } diff --git a/command/server/export/export.go b/command/server/export/export.go index 57c99093c0..58621e9735 100644 --- a/command/server/export/export.go +++ b/command/server/export/export.go @@ -4,10 +4,10 @@ import ( "encoding/json" "errors" "fmt" - "os" "github.com/0xPolygon/polygon-edge/command" "github.com/0xPolygon/polygon-edge/command/server/config" + "github.com/0xPolygon/polygon-edge/helper/common" "github.com/spf13/cobra" "gopkg.in/yaml.v3" ) @@ -71,11 +71,11 @@ func generateConfig(config config.Config) error { return fmt.Errorf("could not marshal config struct, %w", err) } - if err := os.WriteFile( + if err := common.SaveFileSafe( fmt.Sprintf("default-config.%s", paramFlagValues.FileType), data, - os.ModePerm); err != nil { - return errors.New("could not create and write config file") + 0660); err != nil { + return fmt.Errorf("failed to create config file %w", err) } return nil diff --git a/command/server/init.go b/command/server/init.go index f4ca553f93..2ddc2c98d1 100644 --- a/command/server/init.go +++ b/command/server/init.go @@ -61,6 +61,8 @@ func (p *serverParams) initRawParams() error { p.initPeerLimits() p.initLogFileLocation() + p.relayer = p.rawConfig.Relayer + return p.initAddresses() } diff --git a/command/server/params.go b/command/server/params.go index 7a69319a2e..4d8a70ade7 100644 --- a/command/server/params.go +++ b/command/server/params.go @@ -38,6 +38,7 @@ const ( devFlag = "dev" corsOriginFlag = "access-control-allow-origins" logFileLocationFlag = "log-to" + relayerFlag = "relayer" ) // Flags that are deprecated, but need to be preserved for @@ -87,6 +88,8 @@ type serverParams struct { secretsConfig *secrets.SecretsManagerConfig logFileLocation string + + relayer bool } func (p *serverParams) isMaxPeersSet() bool { @@ -178,5 +181,6 @@ func (p *serverParams) generateConfig() *server.Config { LogLevel: hclog.LevelFromString(p.rawConfig.LogLevel), JSONLogFormat: p.rawConfig.JSONLogFormat, LogFilePath: p.logFileLocation, + Relayer: p.relayer, } } diff --git a/command/server/server.go b/command/server/server.go index 368bf4a35e..d0b4d39583 100644 --- a/command/server/server.go +++ b/command/server/server.go @@ -221,6 +221,13 @@ func setFlags(cmd *cobra.Command) { "write all logs to the file at specified location instead of writing them to console", ) + cmd.Flags().BoolVar( + ¶ms.rawConfig.Relayer, + relayerFlag, + defaultConfig.Relayer, + "start the state sync relayer service (PolyBFT only)", + ) + setLegacyFlags(cmd) setDevFlags(cmd) diff --git a/command/sidechain/helper.go b/command/sidechain/helper.go new file mode 100644 index 0000000000..0f3ee8ceb6 --- /dev/null +++ b/command/sidechain/helper.go @@ -0,0 +1,111 @@ +package sidechain + +import ( + "errors" + "fmt" + "math/big" + "os" + + "github.com/0xPolygon/polygon-edge/consensus/polybft" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/helper/hex" + secretsHelper "github.com/0xPolygon/polygon-edge/secrets/helper" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/umbracle/ethgo" +) + +const ( + AccountDirFlag = "account" + SelfFlag = "self" + AmountFlag = "amount" + + DefaultGasPrice = 1879048192 // 0x70000000 +) + +func CheckIfDirectoryExist(dir string) error { + if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("provided directory '%s' doesn't exist", dir) + } + + return nil +} + +func GetAccountFromDir(dir string) (*wallet.Account, error) { + secretsManager, err := secretsHelper.SetupLocalSecretsManager(dir) + if err != nil { + return nil, err + } + + return wallet.NewAccountFromSecret(secretsManager) +} + +// GetValidatorInfo queries ChildValidatorSet smart contract and retrieves validator info for given address +func GetValidatorInfo(validatorAddr ethgo.Address, txRelayer txrelayer.TxRelayer) (*polybft.ValidatorInfo, error) { + getValidatorMethod := contractsapi.ChildValidatorSet.Abi.GetMethod("getValidator") + + encode, err := getValidatorMethod.Encode([]interface{}{validatorAddr}) + if err != nil { + return nil, err + } + + response, err := txRelayer.Call(ethgo.Address(contracts.SystemCaller), + ethgo.Address(contracts.ValidatorSetContract), encode) + if err != nil { + return nil, err + } + + byteResponse, err := hex.DecodeHex(response) + if err != nil { + return nil, fmt.Errorf("unable to decode hex response, %w", err) + } + + decoded, err := getValidatorMethod.Outputs.Decode(byteResponse) + if err != nil { + return nil, err + } + + decodedOutputsMap, ok := decoded.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("could not convert decoded outputs to map") + } + + decodedValidatorInfoMap, ok := decodedOutputsMap["0"].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("could not convert validator info result to a map") + } + + return &polybft.ValidatorInfo{ + Address: validatorAddr.Address(), + Stake: decodedValidatorInfoMap["stake"].(*big.Int), //nolint:forcetypeassert + TotalStake: decodedValidatorInfoMap["totalStake"].(*big.Int), //nolint:forcetypeassert + Commission: decodedValidatorInfoMap["commission"].(*big.Int), //nolint:forcetypeassert + WithdrawableRewards: decodedValidatorInfoMap["withdrawableRewards"].(*big.Int), //nolint:forcetypeassert + Active: decodedValidatorInfoMap["active"].(bool), //nolint:forcetypeassert + }, nil +} + +// GetDelegatorReward queries delegator reward for given validator and delegator addresses +func GetDelegatorReward(validatorAddr ethgo.Address, delegatorAddr ethgo.Address, + txRelayer txrelayer.TxRelayer) (*big.Int, error) { + input, err := contractsapi.ChildValidatorSet.Abi.Methods["getValidatorReward"].Encode( + []interface{}{validatorAddr, delegatorAddr}) + if err != nil { + return nil, fmt.Errorf("failed to encode input parameters for getDelegatorReward fn: %w", err) + } + + response, err := txRelayer.Call(ethgo.Address(contracts.SystemCaller), + ethgo.Address(contracts.ValidatorSetContract), input) + if err != nil { + return nil, err + } + + delegatorReward, err := types.ParseUint256orHex(&response) + if err != nil { + return nil, fmt.Errorf("unable to decode hex response, %w", err) + } + + return delegatorReward, nil +} diff --git a/command/sidechain/staking/params.go b/command/sidechain/staking/params.go new file mode 100644 index 0000000000..204840beaf --- /dev/null +++ b/command/sidechain/staking/params.go @@ -0,0 +1,58 @@ +package staking + +import ( + "bytes" + "fmt" + + "github.com/0xPolygon/polygon-edge/command/helper" + sidechainHelper "github.com/0xPolygon/polygon-edge/command/sidechain" +) + +var ( + delegateAddressFlag = "delegate" +) + +type stakeParams struct { + accountDir string + jsonRPC string + amount uint64 + self bool + delegateAddress string +} + +func (v *stakeParams) validateFlags() error { + return sidechainHelper.CheckIfDirectoryExist(v.accountDir) +} + +type stakeResult struct { + validatorAddress string + isSelfStake bool + amount uint64 + delegatedTo string +} + +func (sr stakeResult) GetOutput() string { + var buffer bytes.Buffer + + var vals []string + + if sr.isSelfStake { + buffer.WriteString("\n[SELF STAKE]\n") + + vals = make([]string, 0, 2) + vals = append(vals, fmt.Sprintf("Validator Address|%s", sr.validatorAddress)) + vals = append(vals, fmt.Sprintf("Amount Staked|%v", sr.amount)) + } else { + buffer.WriteString("\n[DELEGATED AMOUNT]\n") + + vals = make([]string, 0, 3) + vals = append(vals, fmt.Sprintf("Validator Address|%s", sr.validatorAddress)) + vals = append(vals, fmt.Sprintf("Amount Delegated|%v", sr.amount)) + vals = append(vals, fmt.Sprintf("Delegated To|%s", sr.delegatedTo)) + } + + buffer.WriteString(helper.FormatKV(vals)) + buffer.WriteString("\n") + + return buffer.String() +} diff --git a/command/sidechain/staking/stake.go b/command/sidechain/staking/stake.go new file mode 100644 index 0000000000..4ab30098d2 --- /dev/null +++ b/command/sidechain/staking/stake.go @@ -0,0 +1,166 @@ +package staking + +import ( + "fmt" + "math/big" + "time" + + "github.com/0xPolygon/polygon-edge/command" + "github.com/0xPolygon/polygon-edge/command/helper" + sidechainHelper "github.com/0xPolygon/polygon-edge/command/sidechain" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/spf13/cobra" + "github.com/umbracle/ethgo" +) + +var ( + params stakeParams + stakeEventABI = contractsapi.ChildValidatorSet.Abi.Events["Staked"] + delegateEventABI = contractsapi.ChildValidatorSet.Abi.Events["Delegated"] +) + +func GetCommand() *cobra.Command { + stakeCmd := &cobra.Command{ + Use: "stake", + Short: "Stakes the amount sent for validator or delegates its stake to another account", + PreRunE: runPreRun, + RunE: runCommand, + } + + helper.RegisterJSONRPCFlag(stakeCmd) + setFlags(stakeCmd) + + return stakeCmd +} + +func setFlags(cmd *cobra.Command) { + cmd.Flags().StringVar( + ¶ms.accountDir, + sidechainHelper.AccountDirFlag, + "", + "the directory path where validator key is stored", + ) + + cmd.Flags().BoolVar( + ¶ms.self, + sidechainHelper.SelfFlag, + false, + "indicates if its a self stake action", + ) + + cmd.Flags().Uint64Var( + ¶ms.amount, + sidechainHelper.AmountFlag, + 0, + "amount to stake or delegate to another account", + ) + + cmd.Flags().StringVar( + ¶ms.delegateAddress, + delegateAddressFlag, + "", + "account address to which stake should be delegated", + ) + + cmd.MarkFlagsMutuallyExclusive(sidechainHelper.SelfFlag, delegateAddressFlag) +} + +func runPreRun(cmd *cobra.Command, _ []string) error { + params.jsonRPC = helper.GetJSONRPCAddress(cmd) + + return params.validateFlags() +} + +func runCommand(cmd *cobra.Command, _ []string) error { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + validatorAccount, err := sidechainHelper.GetAccountFromDir(params.accountDir) + if err != nil { + return err + } + + txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPC), + txrelayer.WithReceiptTimeout(150*time.Millisecond)) + if err != nil { + return err + } + + var encoded []byte + if params.self { + encoded, err = contractsapi.ChildValidatorSet.Abi.Methods["stake"].Encode([]interface{}{}) + if err != nil { + return err + } + } else { + delegateToAddress := types.StringToAddress(params.delegateAddress) + encoded, err = contractsapi.ChildValidatorSet.Abi.Methods["delegate"].Encode( + []interface{}{ethgo.Address(delegateToAddress), false}) + if err != nil { + return err + } + } + + txn := ðgo.Transaction{ + From: validatorAccount.Ecdsa.Address(), + Input: encoded, + To: (*ethgo.Address)(&contracts.ValidatorSetContract), + Value: big.NewInt(int64(params.amount)), + GasPrice: sidechainHelper.DefaultGasPrice, + } + + receipt, err := txRelayer.SendTransaction(txn, validatorAccount.Ecdsa) + if err != nil { + return err + } + + if receipt.Status == uint64(types.ReceiptFailed) { + return fmt.Errorf("staking transaction failed on block %d", receipt.BlockNumber) + } + + result := &stakeResult{ + validatorAddress: validatorAccount.Ecdsa.Address().String(), + } + + var foundLog bool + + // check the logs to check for the result + for _, log := range receipt.Logs { + if stakeEventABI.Match(log) { + event, err := stakeEventABI.ParseLog(log) + if err != nil { + return err + } + + result.isSelfStake = true + result.amount = event["amount"].(*big.Int).Uint64() //nolint:forcetypeassert + + foundLog = true + + break + } else if delegateEventABI.Match(log) { + event, err := delegateEventABI.ParseLog(log) + if err != nil { + return err + } + + result.amount = event["amount"].(*big.Int).Uint64() //nolint:forcetypeassert + result.delegatedTo = event["validator"].(ethgo.Address).String() //nolint:forcetypeassert + + foundLog = true + + break + } + } + + if !foundLog { + return fmt.Errorf("could not find an appropriate log in receipt that stake or delegate happened") + } + + outputter.WriteCommandResult(result) + + return nil +} diff --git a/command/sidechain/unstaking/params.go b/command/sidechain/unstaking/params.go new file mode 100644 index 0000000000..53e8784aaa --- /dev/null +++ b/command/sidechain/unstaking/params.go @@ -0,0 +1,58 @@ +package unstaking + +import ( + "bytes" + "fmt" + + "github.com/0xPolygon/polygon-edge/command/helper" + sidechainHelper "github.com/0xPolygon/polygon-edge/command/sidechain" +) + +var ( + undelegateAddressFlag = "undelegate" +) + +type unstakeParams struct { + accountDir string + jsonRPC string + amount uint64 + self bool + undelegateAddress string +} + +func (v *unstakeParams) validateFlags() error { + return sidechainHelper.CheckIfDirectoryExist(v.accountDir) +} + +type unstakeResult struct { + validatorAddress string + isSelfUnstake bool + amount uint64 + undelegatedFrom string +} + +func (ur unstakeResult) GetOutput() string { + var buffer bytes.Buffer + + var vals []string + + if ur.isSelfUnstake { + buffer.WriteString("\n[SELF UNSTAKE]\n") + + vals = make([]string, 0, 2) + vals = append(vals, fmt.Sprintf("Validator Address|%s", ur.validatorAddress)) + vals = append(vals, fmt.Sprintf("Amount Unstaked|%v", ur.amount)) + } else { + buffer.WriteString("\n[UNDELEGATED AMOUNT]\n") + + vals = make([]string, 0, 3) + vals = append(vals, fmt.Sprintf("Validator Address|%s", ur.validatorAddress)) + vals = append(vals, fmt.Sprintf("Amount Undelegated|%v", ur.amount)) + vals = append(vals, fmt.Sprintf("Undelegated From|%s", ur.undelegatedFrom)) + } + + buffer.WriteString(helper.FormatKV(vals)) + buffer.WriteString("\n") + + return buffer.String() +} diff --git a/command/sidechain/unstaking/unstake.go b/command/sidechain/unstaking/unstake.go new file mode 100644 index 0000000000..c1e608e4ea --- /dev/null +++ b/command/sidechain/unstaking/unstake.go @@ -0,0 +1,164 @@ +package unstaking + +import ( + "fmt" + "math/big" + "time" + + "github.com/0xPolygon/polygon-edge/command" + "github.com/0xPolygon/polygon-edge/command/helper" + sidechainHelper "github.com/0xPolygon/polygon-edge/command/sidechain" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/spf13/cobra" + "github.com/umbracle/ethgo" +) + +var ( + params unstakeParams + unstakeEventABI = contractsapi.ChildValidatorSet.Abi.Events["Unstaked"] + undelegateEventABI = contractsapi.ChildValidatorSet.Abi.Events["Undelegated"] +) + +func GetCommand() *cobra.Command { + unstakeCmd := &cobra.Command{ + Use: "unstake", + Short: "Unstakes the amount sent for validator or undelegates amount from validator", + PreRunE: runPreRun, + RunE: runCommand, + } + + helper.RegisterJSONRPCFlag(unstakeCmd) + setFlags(unstakeCmd) + + return unstakeCmd +} + +func setFlags(cmd *cobra.Command) { + cmd.Flags().StringVar( + ¶ms.accountDir, + sidechainHelper.AccountDirFlag, + "", + "the directory path where sender account secrets are stored", + ) + + cmd.Flags().BoolVar( + ¶ms.self, + sidechainHelper.SelfFlag, + false, + "indicates if its a self unstake action", + ) + + cmd.Flags().Uint64Var( + ¶ms.amount, + sidechainHelper.AmountFlag, + 0, + "amount to unstake or undelegate amount from validator", + ) + + cmd.Flags().StringVar( + ¶ms.undelegateAddress, + undelegateAddressFlag, + "", + "account address from which amount will be undelegated", + ) + + cmd.MarkFlagsMutuallyExclusive(sidechainHelper.SelfFlag, undelegateAddressFlag) +} + +func runPreRun(cmd *cobra.Command, _ []string) error { + params.jsonRPC = helper.GetJSONRPCAddress(cmd) + + return params.validateFlags() +} + +func runCommand(cmd *cobra.Command, _ []string) error { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + validatorAccount, err := sidechainHelper.GetAccountFromDir(params.accountDir) + if err != nil { + return err + } + + txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPC), + txrelayer.WithReceiptTimeout(150*time.Millisecond)) + if err != nil { + return err + } + + var encoded []byte + if params.self { + encoded, err = contractsapi.ChildValidatorSet.Abi.Methods["unstake"].Encode([]interface{}{params.amount}) + if err != nil { + return err + } + } else { + encoded, err = contractsapi.ChildValidatorSet.Abi.Methods["undelegate"].Encode( + []interface{}{ethgo.HexToAddress(params.undelegateAddress), params.amount}) + if err != nil { + return err + } + } + + txn := ðgo.Transaction{ + From: validatorAccount.Ecdsa.Address(), + Input: encoded, + To: (*ethgo.Address)(&contracts.ValidatorSetContract), + GasPrice: sidechainHelper.DefaultGasPrice, + } + + receipt, err := txRelayer.SendTransaction(txn, validatorAccount.Ecdsa) + if err != nil { + return err + } + + if receipt.Status == uint64(types.ReceiptFailed) { + return fmt.Errorf("unstake transaction failed on block %d", receipt.BlockNumber) + } + + result := &unstakeResult{ + validatorAddress: validatorAccount.Ecdsa.Address().String(), + } + + var foundLog bool + + // check the logs to check for the result + for _, log := range receipt.Logs { + if unstakeEventABI.Match(log) { + event, err := unstakeEventABI.ParseLog(log) + if err != nil { + return err + } + + result.isSelfUnstake = true + result.amount = event["amount"].(*big.Int).Uint64() //nolint:forcetypeassert + + foundLog = true + + break + } else if undelegateEventABI.Match(log) { + event, err := undelegateEventABI.ParseLog(log) + if err != nil { + return err + } + + result.amount = event["amount"].(*big.Int).Uint64() //nolint:forcetypeassert + result.undelegatedFrom = event["validator"].(ethgo.Address).String() //nolint:forcetypeassert + + foundLog = true + + break + } + } + + if !foundLog { + return fmt.Errorf("could not find an appropriate log in receipt that unstake or undelegate happened") + } + + outputter.WriteCommandResult(result) + + return nil +} diff --git a/command/sidechain/validators/params.go b/command/sidechain/validators/params.go new file mode 100644 index 0000000000..f6cdfdbf22 --- /dev/null +++ b/command/sidechain/validators/params.go @@ -0,0 +1,46 @@ +package validators + +import ( + "bytes" + "fmt" + + "github.com/0xPolygon/polygon-edge/command/helper" + sidechainHelper "github.com/0xPolygon/polygon-edge/command/sidechain" +) + +type validatorInfoParams struct { + accountDir string + jsonRPC string +} + +func (v *validatorInfoParams) validateFlags() error { + return sidechainHelper.CheckIfDirectoryExist(v.accountDir) +} + +type validatorsInfoResult struct { + address string + stake uint64 + totalStake uint64 + commission uint64 + withdrawableRewards uint64 + active bool +} + +func (vr validatorsInfoResult) GetOutput() string { + var buffer bytes.Buffer + + buffer.WriteString("\n[VALIDATOR INFO]\n") + + vals := make([]string, 0, 5) + vals = append(vals, fmt.Sprintf("Validator Address|%s", vr.address)) + vals = append(vals, fmt.Sprintf("Self Stake|%v", vr.stake)) + vals = append(vals, fmt.Sprintf("Total Stake|%v", vr.totalStake)) + vals = append(vals, fmt.Sprintf("Withdrawable Rewards|%v", vr.withdrawableRewards)) + vals = append(vals, fmt.Sprintf("Commission|%v", vr.commission)) + vals = append(vals, fmt.Sprintf("Is Active|%v", vr.active)) + + buffer.WriteString(helper.FormatKV(vals)) + buffer.WriteString("\n") + + return buffer.String() +} diff --git a/command/sidechain/validators/validator_info.go b/command/sidechain/validators/validator_info.go new file mode 100644 index 0000000000..5d6c5486b5 --- /dev/null +++ b/command/sidechain/validators/validator_info.go @@ -0,0 +1,77 @@ +package validators + +import ( + "fmt" + + "github.com/0xPolygon/polygon-edge/command" + "github.com/0xPolygon/polygon-edge/command/helper" + sidechainHelper "github.com/0xPolygon/polygon-edge/command/sidechain" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/spf13/cobra" +) + +var ( + params validatorInfoParams +) + +func GetCommand() *cobra.Command { + validatorInfoCmd := &cobra.Command{ + Use: "validator-info", + Short: "Gets validator info", + PreRunE: runPreRun, + RunE: runCommand, + } + + helper.RegisterJSONRPCFlag(validatorInfoCmd) + setFlags(validatorInfoCmd) + + return validatorInfoCmd +} + +func setFlags(cmd *cobra.Command) { + cmd.Flags().StringVar( + ¶ms.accountDir, + sidechainHelper.AccountDirFlag, + "", + "the directory path where validator key is stored", + ) +} + +func runPreRun(cmd *cobra.Command, _ []string) error { + params.jsonRPC = helper.GetJSONRPCAddress(cmd) + + return params.validateFlags() +} + +func runCommand(cmd *cobra.Command, _ []string) error { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + validatorAccount, err := sidechainHelper.GetAccountFromDir(params.accountDir) + if err != nil { + return err + } + + txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPC)) + if err != nil { + return err + } + + validatorAddr := validatorAccount.Ecdsa.Address() + + validatorInfo, err := sidechainHelper.GetValidatorInfo(validatorAddr, txRelayer) + if err != nil { + return fmt.Errorf("failed to get validator info for %s: %w", validatorAddr, err) + } + + outputter.WriteCommandResult(&validatorsInfoResult{ + address: validatorInfo.Address.String(), + stake: validatorInfo.Stake.Uint64(), + totalStake: validatorInfo.TotalStake.Uint64(), + commission: validatorInfo.Commission.Uint64(), + withdrawableRewards: validatorInfo.WithdrawableRewards.Uint64(), + active: validatorInfo.Active, + }) + + return nil +} diff --git a/command/sidechain/withdraw/params.go b/command/sidechain/withdraw/params.go new file mode 100644 index 0000000000..2b6c2155d7 --- /dev/null +++ b/command/sidechain/withdraw/params.go @@ -0,0 +1,45 @@ +package withdraw + +import ( + "bytes" + "fmt" + + "github.com/0xPolygon/polygon-edge/command/helper" + sidechainHelper "github.com/0xPolygon/polygon-edge/command/sidechain" +) + +var ( + addressToFlag = "to" +) + +type withdrawParams struct { + accountDir string + jsonRPC string + addressTo string +} + +func (v *withdrawParams) validateFlags() error { + return sidechainHelper.CheckIfDirectoryExist(v.accountDir) +} + +type withdrawResult struct { + validatorAddress string + amount uint64 + withdrawnTo string +} + +func (wr withdrawResult) GetOutput() string { + var buffer bytes.Buffer + + buffer.WriteString("\n[WITHDRAWN AMOUNT]\n") + + vals := make([]string, 0, 3) + vals = append(vals, fmt.Sprintf("Validator Address|%s", wr.validatorAddress)) + vals = append(vals, fmt.Sprintf("Amount Withdrawn|%v", wr.amount)) + vals = append(vals, fmt.Sprintf("Withdrawn To|%s", wr.withdrawnTo)) + + buffer.WriteString(helper.FormatKV(vals)) + buffer.WriteString("\n") + + return buffer.String() +} diff --git a/command/sidechain/withdraw/withdraw.go b/command/sidechain/withdraw/withdraw.go new file mode 100644 index 0000000000..5433f14376 --- /dev/null +++ b/command/sidechain/withdraw/withdraw.go @@ -0,0 +1,126 @@ +package withdraw + +import ( + "fmt" + "math/big" + "time" + + "github.com/0xPolygon/polygon-edge/command" + "github.com/0xPolygon/polygon-edge/command/helper" + sidechainHelper "github.com/0xPolygon/polygon-edge/command/sidechain" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/spf13/cobra" + "github.com/umbracle/ethgo" +) + +var ( + params withdrawParams + withdrawEventABI = contractsapi.ChildValidatorSet.Abi.Events["Withdrawal"] +) + +func GetCommand() *cobra.Command { + withdrawCmd := &cobra.Command{ + Use: "withdraw", + Short: "Withdraws sender's withdrawable amount to specified address", + PreRunE: runPreRun, + RunE: runCommand, + } + + setFlags(withdrawCmd) + + return withdrawCmd +} + +func setFlags(cmd *cobra.Command) { + cmd.Flags().StringVar( + ¶ms.accountDir, + sidechainHelper.AccountDirFlag, + "", + "the directory path where validator key is stored", + ) + + cmd.Flags().StringVar( + ¶ms.addressTo, + addressToFlag, + "", + "address where to withdraw withdrawable amount", + ) + + helper.RegisterJSONRPCFlag(cmd) +} + +func runPreRun(cmd *cobra.Command, _ []string) error { + params.jsonRPC = helper.GetJSONRPCAddress(cmd) + + return params.validateFlags() +} + +func runCommand(cmd *cobra.Command, _ []string) error { + outputter := command.InitializeOutputter(cmd) + defer outputter.WriteOutput() + + validatorAccount, err := sidechainHelper.GetAccountFromDir(params.accountDir) + if err != nil { + return err + } + + txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPC), + txrelayer.WithReceiptTimeout(150*time.Millisecond)) + if err != nil { + return err + } + + encoded, err := contractsapi.ChildValidatorSet.Abi.Methods["withdraw"].Encode( + []interface{}{ethgo.HexToAddress(params.addressTo)}) + if err != nil { + return err + } + + txn := ðgo.Transaction{ + From: validatorAccount.Ecdsa.Address(), + Input: encoded, + To: (*ethgo.Address)(&contracts.ValidatorSetContract), + GasPrice: sidechainHelper.DefaultGasPrice, + } + + receipt, err := txRelayer.SendTransaction(txn, validatorAccount.Ecdsa) + if err != nil { + return err + } + + if receipt.Status == uint64(types.ReceiptFailed) { + return fmt.Errorf("withdraw transaction failed on block %d", receipt.BlockNumber) + } + + result := &withdrawResult{ + validatorAddress: validatorAccount.Ecdsa.Address().String(), + } + + var foundLog bool + + for _, log := range receipt.Logs { + if withdrawEventABI.Match(log) { + event, err := withdrawEventABI.ParseLog(log) + if err != nil { + return err + } + + result.amount = event["amount"].(*big.Int).Uint64() //nolint:forcetypeassert + result.withdrawnTo = event["to"].(ethgo.Address).String() //nolint:forcetypeassert + foundLog = true + + break + } + } + + if !foundLog { + return fmt.Errorf("could not find an appropriate log in receipt that withdrawal happened") + } + + outputter.WriteCommandResult(result) + + return nil +} diff --git a/consensus/consensus.go b/consensus/consensus.go index 3e1688f338..5a47434b80 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -34,6 +34,9 @@ type Consensus interface { // GetSyncProgression retrieves the current sync progression, if any GetSyncProgression() *progress.Progression + // GetBridgeProvider returns an instance of BridgeDataProvider + GetBridgeProvider() BridgeDataProvider + // Initialize initializes the consensus (e.g. setup data) Initialize() error @@ -55,7 +58,7 @@ type Config struct { // Config defines specific configuration parameters for the consensus Config map[string]interface{} - // Path is the directory path for the consensus protocol tos tore information + // Path is the directory path for the consensus protocol to store information Path string } @@ -74,3 +77,12 @@ type Params struct { // Factory is the factory function to create a discovery consensus type Factory func(*Params) (Consensus, error) + +// BridgeDataProvider is an interface providing bridge related functions +type BridgeDataProvider interface { + // GenerateExit proof generates proof of exit for given exit event + GenerateExitProof(exitID, epoch, checkpointBlock uint64) (types.Proof, error) + + // GetStateSyncProof retrieves the StateSync proof + GetStateSyncProof(stateSyncID uint64) (types.Proof, error) +} diff --git a/consensus/dev/dev.go b/consensus/dev/dev.go index 63b571e06e..e72d5629c1 100644 --- a/consensus/dev/dev.go +++ b/consensus/dev/dev.go @@ -197,7 +197,7 @@ func (d *Dev) writeNewBlock(parent *types.Header) error { Receipts: transition.Receipts(), }) - if err := d.blockchain.VerifyFinalizedBlock(block); err != nil { + if _, err := d.blockchain.VerifyFinalizedBlock(block); err != nil { return err } @@ -242,3 +242,7 @@ func (d *Dev) Close() error { return nil } + +func (d *Dev) GetBridgeProvider() consensus.BridgeDataProvider { + return nil +} diff --git a/consensus/dummy/dummy.go b/consensus/dummy/dummy.go index b8a8b8cd66..fafc2dc6d6 100644 --- a/consensus/dummy/dummy.go +++ b/consensus/dummy/dummy.go @@ -75,6 +75,10 @@ func (d *Dummy) Close() error { return nil } +func (d *Dummy) GetBridgeProvider() consensus.BridgeDataProvider { + return nil +} + func (d *Dummy) run() { d.logger.Info("started") // do nothing diff --git a/consensus/ibft/consensus_backend.go b/consensus/ibft/consensus_backend.go index 7ad6a351ab..087fb024c1 100644 --- a/consensus/ibft/consensus_backend.go +++ b/consensus/ibft/consensus_backend.go @@ -14,13 +14,13 @@ import ( "github.com/0xPolygon/polygon-edge/types" ) -func (i *backendIBFT) BuildProposal(blockNumber uint64) []byte { +func (i *backendIBFT) BuildProposal(view *proto.View) []byte { var ( latestHeader = i.blockchain.Header() latestBlockNumber = latestHeader.Number ) - if latestBlockNumber+1 != blockNumber { + if latestBlockNumber+1 != view.Height { i.logger.Error( "unable to build block, due to lack of parent block", "num", @@ -32,7 +32,7 @@ func (i *backendIBFT) BuildProposal(blockNumber uint64) []byte { block, err := i.buildBlock(latestHeader) if err != nil { - i.logger.Error("cannot build block", "num", blockNumber, "err", err) + i.logger.Error("cannot build block", "num", view.Height, "err", err) return nil } @@ -161,6 +161,11 @@ func (i *backendIBFT) HasQuorum( case proto.MessageType_PREPREPARE: return len(messages) > 0 case proto.MessageType_PREPARE: + // two cases -> first message is MessageType_PREPREPARE, and other -> MessageType_PREPREPARE is not included + if len(messages) > 0 && messages[0].Type == proto.MessageType_PREPREPARE { + return len(messages) >= quorum + } + return len(messages) >= quorum-1 case proto.MessageType_COMMIT, proto.MessageType_ROUND_CHANGE: return len(messages) >= quorum diff --git a/consensus/ibft/fork/helper.go b/consensus/ibft/fork/helper.go index 253512619b..bc00ac31b6 100644 --- a/consensus/ibft/fork/helper.go +++ b/consensus/ibft/fork/helper.go @@ -4,6 +4,7 @@ import ( "encoding/json" "os" + "github.com/0xPolygon/polygon-edge/helper/common" "github.com/0xPolygon/polygon-edge/validators/store/snapshot" ) @@ -53,7 +54,7 @@ func writeDataStore(path string, obj interface{}) error { return err } - if err := os.WriteFile(path, data, os.ModePerm); err != nil { + if err := common.SaveFileSafe(path, data, 0660); err != nil { return err } diff --git a/consensus/ibft/ibft.go b/consensus/ibft/ibft.go index 09182011cd..4be216f3d1 100644 --- a/consensus/ibft/ibft.go +++ b/consensus/ibft/ibft.go @@ -208,16 +208,16 @@ func (i *backendIBFT) Initialize() error { // sync runs the syncer in the background to receive blocks from advanced peers func (i *backendIBFT) startSyncing() { - callInsertBlockHook := func(block *types.Block) bool { - if err := i.currentHooks.PostInsertBlock(block); err != nil { - i.logger.Error("failed to call PostInsertBlock", "height", block.Header.Number, "error", err) + callInsertBlockHook := func(fullBlock *types.FullBlock) bool { + if err := i.currentHooks.PostInsertBlock(fullBlock.Block); err != nil { + i.logger.Error("failed to call PostInsertBlock", "height", fullBlock.Block.Header.Number, "error", err) } - if err := i.updateCurrentModules(block.Number() + 1); err != nil { - i.logger.Error("failed to update sub modules", "height", block.Number()+1, "err", err) + if err := i.updateCurrentModules(fullBlock.Block.Number() + 1); err != nil { + i.logger.Error("failed to update sub modules", "height", fullBlock.Block.Number()+1, "err", err) } - i.txpool.ResetWithHeaders(block.Header) + i.txpool.ResetWithHeaders(fullBlock.Block.Header) return false } @@ -547,6 +547,11 @@ func (i *backendIBFT) SetHeaderHash() { } } +// GetBridgeProvider returns an instance of BridgeDataProvider +func (i *backendIBFT) GetBridgeProvider() consensus.BridgeDataProvider { + return nil +} + // updateCurrentModules updates Signer, Hooks, and Validators // that are used at specified height // by fetching from ForkManager diff --git a/consensus/ibft/proto/ibft_operator.pb.go b/consensus/ibft/proto/ibft_operator.pb.go index 8504770798..2863467466 100644 --- a/consensus/ibft/proto/ibft_operator.pb.go +++ b/consensus/ibft/proto/ibft_operator.pb.go @@ -1,15 +1,15 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 -// protoc v3.12.4 +// protoc v3.21.7 // source: consensus/ibft/proto/ibft_operator.proto package proto import ( - empty "github.com/golang/protobuf/ptypes/empty" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" ) @@ -573,7 +573,7 @@ var file_consensus_ibft_proto_ibft_operator_proto_goTypes = []interface{}{ (*Candidate)(nil), // 5: v1.Candidate (*Snapshot_Validator)(nil), // 6: v1.Snapshot.Validator (*Snapshot_Vote)(nil), // 7: v1.Snapshot.Vote - (*empty.Empty)(nil), // 8: google.protobuf.Empty + (*emptypb.Empty)(nil), // 8: google.protobuf.Empty } var file_consensus_ibft_proto_ibft_operator_proto_depIdxs = []int32{ 6, // 0: v1.Snapshot.validators:type_name -> v1.Snapshot.Validator diff --git a/consensus/ibft/proto/ibft_operator_grpc.pb.go b/consensus/ibft/proto/ibft_operator_grpc.pb.go index 5e0322b92c..dac75a466f 100644 --- a/consensus/ibft/proto/ibft_operator_grpc.pb.go +++ b/consensus/ibft/proto/ibft_operator_grpc.pb.go @@ -1,17 +1,17 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 -// - protoc v3.12.4 +// - protoc v3.21.7 // source: consensus/ibft/proto/ibft_operator.proto package proto import ( context "context" - empty "github.com/golang/protobuf/ptypes/empty" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" ) // This is a compile-time assertion to ensure that this generated file @@ -24,9 +24,9 @@ const _ = grpc.SupportPackageIsVersion7 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type IbftOperatorClient interface { GetSnapshot(ctx context.Context, in *SnapshotReq, opts ...grpc.CallOption) (*Snapshot, error) - Propose(ctx context.Context, in *Candidate, opts ...grpc.CallOption) (*empty.Empty, error) - Candidates(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*CandidatesResp, error) - Status(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*IbftStatusResp, error) + Propose(ctx context.Context, in *Candidate, opts ...grpc.CallOption) (*emptypb.Empty, error) + Candidates(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CandidatesResp, error) + Status(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*IbftStatusResp, error) } type ibftOperatorClient struct { @@ -46,8 +46,8 @@ func (c *ibftOperatorClient) GetSnapshot(ctx context.Context, in *SnapshotReq, o return out, nil } -func (c *ibftOperatorClient) Propose(ctx context.Context, in *Candidate, opts ...grpc.CallOption) (*empty.Empty, error) { - out := new(empty.Empty) +func (c *ibftOperatorClient) Propose(ctx context.Context, in *Candidate, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) err := c.cc.Invoke(ctx, "/v1.IbftOperator/Propose", in, out, opts...) if err != nil { return nil, err @@ -55,7 +55,7 @@ func (c *ibftOperatorClient) Propose(ctx context.Context, in *Candidate, opts .. return out, nil } -func (c *ibftOperatorClient) Candidates(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*CandidatesResp, error) { +func (c *ibftOperatorClient) Candidates(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CandidatesResp, error) { out := new(CandidatesResp) err := c.cc.Invoke(ctx, "/v1.IbftOperator/Candidates", in, out, opts...) if err != nil { @@ -64,7 +64,7 @@ func (c *ibftOperatorClient) Candidates(ctx context.Context, in *empty.Empty, op return out, nil } -func (c *ibftOperatorClient) Status(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*IbftStatusResp, error) { +func (c *ibftOperatorClient) Status(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*IbftStatusResp, error) { out := new(IbftStatusResp) err := c.cc.Invoke(ctx, "/v1.IbftOperator/Status", in, out, opts...) if err != nil { @@ -78,9 +78,9 @@ func (c *ibftOperatorClient) Status(ctx context.Context, in *empty.Empty, opts . // for forward compatibility type IbftOperatorServer interface { GetSnapshot(context.Context, *SnapshotReq) (*Snapshot, error) - Propose(context.Context, *Candidate) (*empty.Empty, error) - Candidates(context.Context, *empty.Empty) (*CandidatesResp, error) - Status(context.Context, *empty.Empty) (*IbftStatusResp, error) + Propose(context.Context, *Candidate) (*emptypb.Empty, error) + Candidates(context.Context, *emptypb.Empty) (*CandidatesResp, error) + Status(context.Context, *emptypb.Empty) (*IbftStatusResp, error) mustEmbedUnimplementedIbftOperatorServer() } @@ -91,13 +91,13 @@ type UnimplementedIbftOperatorServer struct { func (UnimplementedIbftOperatorServer) GetSnapshot(context.Context, *SnapshotReq) (*Snapshot, error) { return nil, status.Errorf(codes.Unimplemented, "method GetSnapshot not implemented") } -func (UnimplementedIbftOperatorServer) Propose(context.Context, *Candidate) (*empty.Empty, error) { +func (UnimplementedIbftOperatorServer) Propose(context.Context, *Candidate) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method Propose not implemented") } -func (UnimplementedIbftOperatorServer) Candidates(context.Context, *empty.Empty) (*CandidatesResp, error) { +func (UnimplementedIbftOperatorServer) Candidates(context.Context, *emptypb.Empty) (*CandidatesResp, error) { return nil, status.Errorf(codes.Unimplemented, "method Candidates not implemented") } -func (UnimplementedIbftOperatorServer) Status(context.Context, *empty.Empty) (*IbftStatusResp, error) { +func (UnimplementedIbftOperatorServer) Status(context.Context, *emptypb.Empty) (*IbftStatusResp, error) { return nil, status.Errorf(codes.Unimplemented, "method Status not implemented") } func (UnimplementedIbftOperatorServer) mustEmbedUnimplementedIbftOperatorServer() {} @@ -150,7 +150,7 @@ func _IbftOperator_Propose_Handler(srv interface{}, ctx context.Context, dec fun } func _IbftOperator_Candidates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(empty.Empty) + in := new(emptypb.Empty) if err := dec(in); err != nil { return nil, err } @@ -162,13 +162,13 @@ func _IbftOperator_Candidates_Handler(srv interface{}, ctx context.Context, dec FullMethod: "/v1.IbftOperator/Candidates", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(IbftOperatorServer).Candidates(ctx, req.(*empty.Empty)) + return srv.(IbftOperatorServer).Candidates(ctx, req.(*emptypb.Empty)) } return interceptor(ctx, in, info, handler) } func _IbftOperator_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(empty.Empty) + in := new(emptypb.Empty) if err := dec(in); err != nil { return nil, err } @@ -180,7 +180,7 @@ func _IbftOperator_Status_Handler(srv interface{}, ctx context.Context, dec func FullMethod: "/v1.IbftOperator/Status", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(IbftOperatorServer).Status(ctx, req.(*empty.Empty)) + return srv.(IbftOperatorServer).Status(ctx, req.(*emptypb.Empty)) } return interceptor(ctx, in, info, handler) } diff --git a/consensus/polybft/README.md b/consensus/polybft/README.md new file mode 100644 index 0000000000..c1b5f1935d --- /dev/null +++ b/consensus/polybft/README.md @@ -0,0 +1,84 @@ + +# Polybft consensus protocol + +Polybft is a consensus protocol, which runs [go-ibft](https://github.com/0xPolygon/go-ibft) consensus engine. + +It has native support for running bridge, which enables running cross-chain transactions with Ethereum-compatible blockchains. + +## Setup local testing environment + +### Precondition + +Smart contracts in the `core-contracts` submodule must be compiled before running following commands. +In order to do so, run `make compile-core-contracts`. + +1. Build binary + + ```bash + go build -o polygon-edge . + ``` + +2. Init secrets - this command is used to generate private keys (ECDSA, BLS as well as P2P networking node id). `--data-dir` denotes folder prefix names and `--num` how many accounts need to be created. **This command is for testing purposes only.** + + ```bash + polygon-edge polybft-secrets --data-dir test-chain- --num 4 + ``` + +3. Start rootchain server - rootchain server is a Geth instance running in dev mode, which simulates Ethereum network. **This command is for testing purposes only.** + + ```bash + polygon-edge rootchain server + ``` + +4. Generate manifest file - manifest file contains public validator information as well as bridge configuration. It is intermediary file which is later used for genesis specification generation as well as rootchain contracts deployment. + + There are two ways to provide validators information: + + - all the validators information is present in local storage of single host and therefore directory if provided using `--validators-path` flag and validators folder prefix names using `--validators-prefix` flag + + ```bash + polygon-edge manifest [--validators-path ./] [--validators-prefix test-chain-] + [--path ./manifest.json] [--premine-validators 100] + ``` + + - validators information are scafollded on multiple hosts and therefore we supply necessary information using `--validators` flag. Validator information needs to be supplied in the strictly following format: + `::`. + + ```bash + polygon-edge manifest + --validators 16Uiu2HAmTkqGixWVxshMbbgtXhTUP8zLCZZiG1UyCNtxLhCkZJuv:0xDcBe0024206ec42b0Ef4214Ac7B71aeae1A11af0:1cf134e02c6b2afb2ceda50bf2c9a01da367ac48f7783ee6c55444e1cab418ec0f52837b90a4d8cf944814073fc6f2bd96f35366a3846a8393e3cb0b19197cde23e2b40c6401fa27ff7d0c36779d9d097d1393cab6fc1d332f92fb3df850b78703b2989d567d1344e219f0667a1863f52f7663092276770cf513f9704b5351c4 + [--validators 16Uiu2HAm1kVEh4uVw41WuhDfreCaVuj3kiWZy44kbnJrZnwnMKDW:0x2da750eD4AE1D5A7F7c996Faec592F3d44060e90:088d92c25b5f278750534e8a902da604a1aa39b524b4511f5f47c3a386374ca3031b667beb424faef068a01cee3428a1bc8c1c8bab826f30a1ee03fbe90cb5f01abcf4abd7af3bbe83eaed6f82179b9cbdc417aad65d919b802d91c2e1aaefec27ba747158bc18a0556e39bfc9175c099dd77517a85731894bbea3d191a622bc] + [--path ./manifest.json] [--premine-validators 100] + ``` + +5. Deploy and initialize rootchain contracts - this command deploys rootchain smart contracts and initializes them. It also updates manifest configuration with rootchain contract addresses and rootchain default sender address. + + ```bash + polygon-edge rootchain init-contracts [--manifest ./manifest.json] [--contracts-path ./core-contracts/artifacts] + [--json-rpc http://127.0.0.1:8545] [--admin-key ] + ``` + +6. Create chain configuration - this command creates chain configuration, which is needed to run a blockchain + + ```bash + polygon-edge genesis --consensus polybft --block-gas-limit 10000000 --epoch-size 10 + [--bridge-json-rpc ] [--contracts-path ./core-contracts/artifacts] [--manifest ./manifest.json] + ``` + +7. Fund validators on rootchain - in order for validators to be able to send transactions to Ethereum, they need to be funded in order to be able to cover gas cost. **This command is for testing purposes only.** + + ```bash + polygon-edge rootchain fund --data-dir test-chain- --num 4 + ``` + +8. Run (sidechain) cluster, consisting of 4 Edge clients in this particular example + + ```bash + polygon-edge server --data-dir ./test-chain-1 --chain genesis.json --grpc-address :5001 --libp2p :30301 --jsonrpc :9545 --seal --log-level DEBUG + + polygon-edge server --data-dir ./test-chain-2 --chain genesis.json --grpc-address :5002 --libp2p :30302 --jsonrpc :10002 --seal --log-level DEBUG + + polygon-edge server --data-dir ./test-chain-3 --chain genesis.json --grpc-address :5003 --libp2p :30303 --jsonrpc :10003 --seal --log-level DEBUG + + polygon-edge server --data-dir ./test-chain-4 --chain genesis.json --grpc-address :5004 --libp2p :30304 --jsonrpc :10004 --seal --log-level DEBUG + ``` diff --git a/consensus/polybft/bitmap/bitmap.go b/consensus/polybft/bitmap/bitmap.go new file mode 100644 index 0000000000..5e56fea677 --- /dev/null +++ b/consensus/polybft/bitmap/bitmap.go @@ -0,0 +1,37 @@ +package bitmap + +// Bitmap Index 0 is LSB from the first bitmap byte +type Bitmap []byte + +func (b *Bitmap) Set(idx uint64) { + index := idx / 8 + *b = extendByteSlice(*b, int(index)+1) + + bit := uint8(1 << (idx % 8)) + (*b)[idx/8] |= bit +} + +func (b *Bitmap) Len() uint64 { + return uint64(len(*b) * 8) +} + +func (b *Bitmap) IsSet(idx uint64) bool { + if b.Len() <= idx { + return false + } + + bit := uint8(1 << (idx % 8)) + + return (*b)[idx/8]&bit == bit +} + +func extendByteSlice(b []byte, needLen int) []byte { + // for example if we need to store idx 277 we need 35 bytes + // But if have slice which length is 5 bytes we need to add additional 30 bytes + // append function is smart enough to use capacity of slice if needed otherwise it will create new slice + if n := needLen - len(b); n > 0 { + b = append(b, make([]byte, n)...) + } + + return b +} diff --git a/consensus/polybft/bitmap/bitmap_test.go b/consensus/polybft/bitmap/bitmap_test.go new file mode 100644 index 0000000000..2d01027e62 --- /dev/null +++ b/consensus/polybft/bitmap/bitmap_test.go @@ -0,0 +1,63 @@ +package bitmap + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestBitmap_Set(t *testing.T) { + t.Parallel() + + data := []int{8, 15, 0, 7, 31, 60, 112, 7, 16, 241, 189, 60, 0, 19, 14, 25} + unique := map[int]struct{}{} + + b := Bitmap{} + + for _, v := range data { + unique[v] = struct{}{} + + b.Set(uint64(v)) + } + + // check if only values from data are populated + for _, v := range data { + require.True(t, b.IsSet(uint64(v))) + } + + cntSet := 0 + + for i := uint64(0); i < b.Len(); i++ { + if _, exists := unique[int(i)]; exists { + cntSet++ + } + } + + require.Equal(t, len(unique), cntSet) +} + +func TestBitmap_Extend(t *testing.T) { + t.Parallel() + + data := []struct { + index uint64 + length uint64 + }{ + {0, 1}, + {8, 2}, + {15, 2}, + {30, 4}, + {17, 4}, + {120, 16}, + {39, 16}, + {277, 35}, + {8, 35}, + } + b := Bitmap{} + + for _, dt := range data { + b.Set(dt.index) + require.True(t, b.Len() == dt.length*8, "assertion failed when setting index %d", dt.index) + require.Len(t, b, int(dt.length)) + } +} diff --git a/consensus/polybft/block_builder.go b/consensus/polybft/block_builder.go new file mode 100644 index 0000000000..d0a6f75997 --- /dev/null +++ b/consensus/polybft/block_builder.go @@ -0,0 +1,211 @@ +package polybft + +import ( + "time" + + "github.com/0xPolygon/polygon-edge/consensus" + "github.com/0xPolygon/polygon-edge/state" + "github.com/0xPolygon/polygon-edge/txpool" + "github.com/0xPolygon/polygon-edge/types" + hcf "github.com/hashicorp/go-hclog" +) + +// TODO: Add opentracing + +// BlockBuilderParams are fields for the block that cannot be changed +type BlockBuilderParams struct { + // Parent block + Parent *types.Header + + // Executor + Executor *state.Executor + + // Coinbase that is signing the block + Coinbase types.Address + + // GasLimit is the gas limit for the block + GasLimit uint64 + + // duration for one block + BlockTime time.Duration + + // Logger + Logger hcf.Logger + + // txPoolInterface implementation + TxPool txPoolInterface +} + +func NewBlockBuilder(params *BlockBuilderParams) *BlockBuilder { + return &BlockBuilder{ + params: params, + } +} + +var _ blockBuilder = &BlockBuilder{} + +type BlockBuilder struct { + // input params for the block + params *BlockBuilderParams + + // header is the header for the block being created + header *types.Header + + // transactions are the data included in the block + txns []*types.Transaction + + // block is a reference to the already built block + block *types.Block + + // state is in memory state transition + state *state.Transition +} + +// Init initializes block builder before adding transactions and actual block building +func (b *BlockBuilder) Reset() error { + // set the timestamp + parentTime := time.Unix(int64(b.params.Parent.Timestamp), 0) + headerTime := parentTime.Add(b.params.BlockTime) + + if headerTime.Before(time.Now()) { + headerTime = time.Now() + } + + b.header = &types.Header{ + ParentHash: b.params.Parent.Hash, + Number: b.params.Parent.Number + 1, + Miner: b.params.Coinbase[:], + Difficulty: 1, + StateRoot: types.EmptyRootHash, // this avoids needing state for now + TxRoot: types.EmptyRootHash, + ReceiptsRoot: types.EmptyRootHash, // this avoids needing state for now + Sha3Uncles: types.EmptyUncleHash, + GasLimit: b.params.GasLimit, + Timestamp: uint64(headerTime.Unix()), + } + + transition, err := b.params.Executor.BeginTxn(b.params.Parent.StateRoot, b.header, b.params.Coinbase) + if err != nil { + return err + } + + b.state = transition + b.block = nil + b.txns = []*types.Transaction{} + + return nil +} + +// Block returns the built block if nil, it is not built yet +func (b *BlockBuilder) Block() *types.Block { + return b.block +} + +// Build creates the state and the final block +func (b *BlockBuilder) Build(handler func(h *types.Header)) (*types.FullBlock, error) { + if handler != nil { + handler(b.header) + } + + _, b.header.StateRoot = b.state.Commit() + b.header.GasUsed = b.state.TotalGas() + + // build the block + b.block = consensus.BuildBlock(consensus.BuildBlockParams{ + Header: b.header, + Txns: b.txns, + Receipts: b.state.Receipts(), + }) + + b.block.Header.ComputeHash() + + return &types.FullBlock{ + Block: b.block, + Receipts: b.state.Receipts(), + }, nil +} + +// WriteTx applies given transaction to the state. If transaction apply fails, it reverts the saved snapshot. +func (b *BlockBuilder) WriteTx(tx *types.Transaction) error { + if tx.ExceedsBlockGasLimit(b.params.GasLimit) { + if err := b.state.WriteFailedReceipt(tx); err != nil { + return err + } + + return txpool.ErrBlockLimitExceeded + } + + if err := b.state.Write(tx); err != nil { + return err + } + + b.txns = append(b.txns, tx) + + return nil +} + +// Fill fills the block with transactions from the txpool +func (b *BlockBuilder) Fill() { + blockTimer := time.NewTimer(b.params.BlockTime) + + b.params.TxPool.Prepare() +write: + for { + select { + case <-blockTimer.C: + return + default: + tx := b.params.TxPool.Peek() + + // execute transactions one by one + finished, err := b.writeTxPoolTransaction(tx) + if err != nil { + b.params.Logger.Debug("Fill transaction error", "hash", tx.Hash, "err", err) + } + + if finished { + break write + } + } + } + + // wait for the timer to expire + <-blockTimer.C +} + +// Receipts returns the collection of transaction receipts for given block +func (b *BlockBuilder) Receipts() []*types.Receipt { + return b.state.Receipts() +} + +func (b *BlockBuilder) writeTxPoolTransaction(tx *types.Transaction) (bool, error) { + if tx == nil { + return true, nil + } + + err := b.WriteTx(tx) + if err != nil { + if _, ok := err.(*state.GasLimitReachedTransitionApplicationError); ok { //nolint:errorlint + // stop processing + return true, err + } else if appErr, ok := err.(*state.TransitionApplicationError); ok && appErr.IsRecoverable { //nolint:errorlint + b.params.TxPool.Demote(tx) + + return false, err + } else { + b.params.TxPool.Drop(tx) + + return false, err + } + } + + // remove tx from the pool and add it to the list of all block transactions + b.params.TxPool.Pop(tx) + + return false, nil +} + +// GetState returns Transition reference +func (b *BlockBuilder) GetState() *state.Transition { + return b.state +} diff --git a/consensus/polybft/blockchain_wrapper.go b/consensus/polybft/blockchain_wrapper.go new file mode 100644 index 0000000000..2af57a5b78 --- /dev/null +++ b/consensus/polybft/blockchain_wrapper.go @@ -0,0 +1,212 @@ +package polybft + +import ( + "errors" + "fmt" + "math/big" + "time" + + "github.com/hashicorp/go-hclog" + + "github.com/0xPolygon/polygon-edge/blockchain" + "github.com/0xPolygon/polygon-edge/consensus" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/state" + "github.com/0xPolygon/polygon-edge/types" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/contract" +) + +const ( + consensusSource = "consensus" +) + +var ( + errSendTxnUnsupported = errors.New("system state does not support send transactions") +) + +// blockchain is an interface that wraps the methods called on blockchain +type blockchainBackend interface { + // CurrentHeader returns the header of blockchain block head + CurrentHeader() *types.Header + + // CommitBlock commits a block to the chain. + CommitBlock(block *types.FullBlock) error + + // NewBlockBuilder is a factory method that returns a block builder on top of 'parent'. + NewBlockBuilder(parent *types.Header, coinbase types.Address, + txPool txPoolInterface, blockTime time.Duration, logger hclog.Logger) (blockBuilder, error) + + // ProcessBlock builds a final block from given 'block' on top of 'parent'. + ProcessBlock(parent *types.Header, block *types.Block, + callback func(*state.Transition) error) (*types.FullBlock, error) + + // GetStateProviderForBlock returns a reference to make queries to the state at 'block'. + GetStateProviderForBlock(block *types.Header) (contract.Provider, error) + + // GetStateProvider returns a reference to make queries to the provided state. + GetStateProvider(transition *state.Transition) contract.Provider + + // GetHeaderByNumber returns a reference to block header for the given block number. + GetHeaderByNumber(number uint64) (*types.Header, bool) + + // GetHeaderByHash returns a reference to block header for the given block hash + GetHeaderByHash(hash types.Hash) (*types.Header, bool) + + // GetSystemState creates a new instance of SystemState interface + GetSystemState(config *PolyBFTConfig, provider contract.Provider) SystemState + + SubscribeEvents() blockchain.Subscription + + // GetChainID returns chain id of the current blockchain + GetChainID() uint64 +} + +var _ blockchainBackend = &blockchainWrapper{} + +type blockchainWrapper struct { + executor *state.Executor + blockchain *blockchain.Blockchain +} + +// CurrentHeader returns the header of blockchain block head +func (p *blockchainWrapper) CurrentHeader() *types.Header { + return p.blockchain.Header() +} + +// CommitBlock commits a block to the chain +func (p *blockchainWrapper) CommitBlock(block *types.FullBlock) error { + if err := p.blockchain.WriteFullBlock(block, consensusSource); err != nil { + return err + } + + return nil +} + +// ProcessBlock builds a final block from given 'block' on top of 'parent' +func (p *blockchainWrapper) ProcessBlock(parent *types.Header, block *types.Block, + callback func(*state.Transition) error) (*types.FullBlock, error) { + header := block.Header.Copy() + + transition, err := p.executor.BeginTxn(parent.StateRoot, header, types.BytesToAddress(header.Miner)) + if err != nil { + return nil, err + } + + // apply transactions from block + for _, tx := range block.Transactions { + if err := transition.Write(tx); err != nil { + return nil, fmt.Errorf("process block tx error, tx = %v, err = %w", tx.Hash, err) + } + } + + if callback != nil { + if err := callback(transition); err != nil { + return nil, err + } + } + + _, root := transition.Commit() + + if root != block.Header.StateRoot { + return nil, fmt.Errorf("incorrect state root: (%s, %s)", root, block.Header.StateRoot) + } + + // build the block + builtBlock := consensus.BuildBlock(consensus.BuildBlockParams{ + Header: header, + Txns: block.Transactions, + Receipts: transition.Receipts(), + }) + + return &types.FullBlock{ + Block: builtBlock, + Receipts: transition.Receipts(), + }, nil +} + +// GetStateProviderForBlock is an implementation of blockchainBackend interface +func (p *blockchainWrapper) GetStateProviderForBlock(header *types.Header) (contract.Provider, error) { + transition, err := p.executor.BeginTxn(header.StateRoot, header, types.ZeroAddress) + if err != nil { + return nil, err + } + + return NewStateProvider(transition), nil +} + +// GetStateProvider returns a reference to make queries to the provided state +func (p *blockchainWrapper) GetStateProvider(transition *state.Transition) contract.Provider { + return NewStateProvider(transition) +} + +// GetHeaderByNumber is an implementation of blockchainBackend interface +func (p *blockchainWrapper) GetHeaderByNumber(number uint64) (*types.Header, bool) { + return p.blockchain.GetHeaderByNumber(number) +} + +// GetHeaderByHash is an implementation of blockchainBackend interface +func (p *blockchainWrapper) GetHeaderByHash(hash types.Hash) (*types.Header, bool) { + return p.blockchain.GetHeaderByHash(hash) +} + +// NewBlockBuilder is an implementation of blockchainBackend interface +func (p *blockchainWrapper) NewBlockBuilder( + parent *types.Header, coinbase types.Address, + txPool txPoolInterface, blockTime time.Duration, logger hclog.Logger) (blockBuilder, error) { + gasLimit, err := p.blockchain.CalculateGasLimit(parent.Number + 1) + if err != nil { + return nil, err + } + + return NewBlockBuilder(&BlockBuilderParams{ + BlockTime: blockTime, + Parent: parent, + Coinbase: coinbase, + Executor: p.executor, + GasLimit: gasLimit, + TxPool: txPool, + Logger: logger, + }), nil +} + +// GetSystemState is an implementation of blockchainBackend interface +func (p *blockchainWrapper) GetSystemState(config *PolyBFTConfig, provider contract.Provider) SystemState { + return NewSystemState(config, provider) +} + +func (p *blockchainWrapper) SubscribeEvents() blockchain.Subscription { + return p.blockchain.SubscribeEvents() +} + +func (p *blockchainWrapper) GetChainID() uint64 { + return uint64(p.blockchain.Config().ChainID) +} + +var _ contract.Provider = &stateProvider{} + +type stateProvider struct { + transition *state.Transition +} + +// NewStateProvider initializes EVM against given state and chain config and returns stateProvider instance +// which is an abstraction for smart contract calls +func NewStateProvider(transition *state.Transition) contract.Provider { + return &stateProvider{transition: transition} +} + +// Call implements the contract.Provider interface to make contract calls directly to the state +func (s *stateProvider) Call(addr ethgo.Address, input []byte, opts *contract.CallOpts) ([]byte, error) { + result := s.transition.Call2(contracts.SystemCaller, types.Address(addr), input, big.NewInt(0), 10000000) + if result.Failed() { + return nil, result.Err + } + + return result.ReturnValue, nil +} + +// Txn is part of the contract.Provider interface to make Ethereum transactions. We disable this function +// since the system state does not make any transaction +func (s *stateProvider) Txn(ethgo.Address, ethgo.Key, []byte) (contract.Txn, error) { + panic(errSendTxnUnsupported) +} diff --git a/consensus/polybft/checkpoint_manager.go b/consensus/polybft/checkpoint_manager.go new file mode 100644 index 0000000000..8d1555d6a3 --- /dev/null +++ b/consensus/polybft/checkpoint_manager.go @@ -0,0 +1,398 @@ +package polybft + +import ( + "bytes" + "fmt" + "math/big" + "sort" + "strconv" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" + metrics "github.com/armon/go-metrics" + hclog "github.com/hashicorp/go-hclog" + "github.com/umbracle/ethgo" +) + +var ( + // currentCheckpointBlockNumMethod is an ABI method object representation for + // currentCheckpointBlockNumber getter function on CheckpointManager contract + currentCheckpointBlockNumMethod, _ = contractsapi.CheckpointManager.Abi.Methods["currentCheckpointBlockNumber"] + // frequency at which checkpoints are sent to the rootchain (in blocks count) + defaultCheckpointsOffset = uint64(900) +) + +type CheckpointManager interface { + PostBlock(req *PostBlockRequest) error + BuildEventRoot(epoch uint64) (types.Hash, error) + GenerateExitProof(exitID, epoch, checkpointBlock uint64) (types.Proof, error) +} + +var _ CheckpointManager = (*dummyCheckpointManager)(nil) + +type dummyCheckpointManager struct{} + +func (d *dummyCheckpointManager) PostBlock(req *PostBlockRequest) error { return nil } +func (d *dummyCheckpointManager) BuildEventRoot(epoch uint64) (types.Hash, error) { + return types.ZeroHash, nil +} +func (d *dummyCheckpointManager) GenerateExitProof(exitID, epoch, checkpointBlock uint64) (types.Proof, error) { + return types.Proof{}, nil +} + +var _ CheckpointManager = (*checkpointManager)(nil) + +// checkpointManager encapsulates logic for checkpoint data submission +type checkpointManager struct { + // key is the identity of the node submitting a checkpoint + key ethgo.Key + // blockchain is abstraction for blockchain + blockchain blockchainBackend + // consensusBackend is abstraction for polybft consensus specific functions + consensusBackend polybftBackend + // txRelayer abstracts rootchain interaction logic (Call and SendTransaction invocations to the rootchain) + txRelayer txrelayer.TxRelayer + // checkpointsOffset represents offset between checkpoint blocks (applicable only for non-epoch ending blocks) + checkpointsOffset uint64 + // checkpointManagerAddr is address of CheckpointManager smart contract + checkpointManagerAddr types.Address + // lastSentBlock represents the last block on which a checkpoint transaction was sent + lastSentBlock uint64 + // logger instance + logger hclog.Logger + // state boltDb instance + state *State +} + +// newCheckpointManager creates a new instance of checkpointManager +func newCheckpointManager(key ethgo.Key, checkpointOffset uint64, + checkpointManagerSC types.Address, txRelayer txrelayer.TxRelayer, + blockchain blockchainBackend, backend polybftBackend, logger hclog.Logger, + state *State) *checkpointManager { + return &checkpointManager{ + key: key, + blockchain: blockchain, + consensusBackend: backend, + txRelayer: txRelayer, + checkpointsOffset: checkpointOffset, + checkpointManagerAddr: checkpointManagerSC, + logger: logger, + state: state, + } +} + +// getLatestCheckpointBlock queries CheckpointManager smart contract and retrieves latest checkpoint block number +func (c *checkpointManager) getLatestCheckpointBlock() (uint64, error) { + checkpointBlockNumMethodEncoded, err := currentCheckpointBlockNumMethod.Encode([]interface{}{}) + if err != nil { + return 0, fmt.Errorf("failed to encode currentCheckpointId function parameters: %w", err) + } + + latestCheckpointBlockRaw, err := c.txRelayer.Call( + c.key.Address(), + ethgo.Address(c.checkpointManagerAddr), + checkpointBlockNumMethodEncoded) + if err != nil { + return 0, fmt.Errorf("failed to invoke currentCheckpointId function on the rootchain: %w", err) + } + + latestCheckpointBlockNum, err := strconv.ParseUint(latestCheckpointBlockRaw, 0, 64) + if err != nil { + return 0, fmt.Errorf("failed to convert current checkpoint id '%s' to number: %w", + latestCheckpointBlockRaw, err) + } + + return latestCheckpointBlockNum, nil +} + +// submitCheckpoint sends a transaction with checkpoint data to the rootchain +func (c *checkpointManager) submitCheckpoint(latestHeader *types.Header, isEndOfEpoch bool) error { + lastCheckpointBlockNumber, err := c.getLatestCheckpointBlock() + if err != nil { + return err + } + + c.logger.Debug("submitCheckpoint invoked...", + "latest checkpoint block", lastCheckpointBlockNumber, + "checkpoint block", latestHeader.Number) + + checkpointManagerAddr := ethgo.Address(c.checkpointManagerAddr) + txn := ðgo.Transaction{ + To: &checkpointManagerAddr, + From: c.key.Address(), + } + initialBlockNumber := lastCheckpointBlockNumber + 1 + + var ( + parentExtra *Extra + parentHeader *types.Header + currentExtra *Extra + ) + + if initialBlockNumber < latestHeader.Number { + found := false + parentHeader, found = c.blockchain.GetHeaderByNumber(initialBlockNumber) + + if !found { + return fmt.Errorf("block %d was not found", initialBlockNumber) + } + + parentExtra, err = GetIbftExtra(parentHeader.ExtraData) + if err != nil { + return err + } + } + + // detect any pending (previously failed) checkpoints and send them + for blockNumber := initialBlockNumber + 1; blockNumber <= latestHeader.Number; blockNumber++ { + currentHeader, found := c.blockchain.GetHeaderByNumber(blockNumber) + if !found { + return fmt.Errorf("block %d was not found", blockNumber) + } + + currentExtra, err = GetIbftExtra(currentHeader.ExtraData) + if err != nil { + return err + } + + parentEpochNumber := parentExtra.Checkpoint.EpochNumber + currentEpochNumber := currentExtra.Checkpoint.EpochNumber + // send pending checkpoints only for epoch ending blocks + if blockNumber == 1 || parentEpochNumber == currentEpochNumber { + parentHeader = currentHeader + parentExtra = currentExtra + + continue + } + + if err = c.encodeAndSendCheckpoint(txn, parentHeader, parentExtra, true); err != nil { + return err + } + + parentHeader = currentHeader + parentExtra = currentExtra + } + + // latestHeader extra could be set in the for loop above + // (in case there were pending checkpoint blocks) + if currentExtra == nil { + // we need to send checkpoint for the latest block + currentExtra, err = GetIbftExtra(latestHeader.ExtraData) + if err != nil { + return err + } + } + + return c.encodeAndSendCheckpoint(txn, latestHeader, currentExtra, isEndOfEpoch) +} + +// encodeAndSendCheckpoint encodes checkpoint data for the given block and +// sends a transaction to the CheckpointManager rootchain contract +func (c *checkpointManager) encodeAndSendCheckpoint(txn *ethgo.Transaction, + header *types.Header, extra *Extra, isEndOfEpoch bool) error { + c.logger.Debug("send checkpoint txn...", "block number", header.Number) + + nextEpochValidators := AccountSet{} + + if isEndOfEpoch { + var err error + nextEpochValidators, err = c.consensusBackend.GetValidators(header.Number, nil) + + if err != nil { + return err + } + } + + input, err := c.abiEncodeCheckpointBlock(header.Number, header.Hash, extra, nextEpochValidators) + if err != nil { + return fmt.Errorf("failed to encode checkpoint data to ABI for block %d: %w", header.Number, err) + } + + txn.Input = input + + receipt, err := c.txRelayer.SendTransaction(txn, c.key) + if err != nil { + return err + } + + if receipt.Status == uint64(types.ReceiptFailed) { + return fmt.Errorf("checkpoint submission transaction failed for block %d", header.Number) + } + + // update checkpoint block number metrics + metrics.SetGauge([]string{"bridge", "checkpoint_block_number"}, float32(header.Number)) + c.logger.Debug("send checkpoint txn success", "block number", header.Number) + + return nil +} + +// abiEncodeCheckpointBlock encodes checkpoint data into ABI format for a given header +func (c *checkpointManager) abiEncodeCheckpointBlock(blockNumber uint64, blockHash types.Hash, extra *Extra, + nextValidators AccountSet) ([]byte, error) { + aggs, err := bls.UnmarshalSignature(extra.Committed.AggregatedSignature) + if err != nil { + return nil, err + } + + encodedAggSigs, err := aggs.ToBigInt() + if err != nil { + return nil, err + } + + submit := &contractsapi.SubmitFunction{ + ChainID: new(big.Int).SetUint64(c.blockchain.GetChainID()), + CheckpointMetadata: &contractsapi.CheckpointMetadata{ + BlockHash: blockHash, + BlockRound: new(big.Int).SetUint64(extra.Checkpoint.BlockRound), + CurrentValidatorSetHash: extra.Checkpoint.CurrentValidatorsHash, + }, + Checkpoint: &contractsapi.Checkpoint{ + Epoch: new(big.Int).SetUint64(extra.Checkpoint.EpochNumber), + BlockNumber: new(big.Int).SetUint64(blockNumber), + EventRoot: extra.Checkpoint.EventRoot, + }, + Signature: encodedAggSigs, + Bitmap: extra.Committed.Bitmap, + NewValidatorSet: nextValidators.ToAPIBinding(), + } + + return submit.EncodeAbi() +} + +// isCheckpointBlock returns true for blocks in the middle of the epoch +// which are offset by predefined count of blocks +// or if given block is an epoch ending block +func (c *checkpointManager) isCheckpointBlock(blockNumber uint64, isEpochEndingBlock bool) bool { + return isEpochEndingBlock || blockNumber == c.lastSentBlock+c.checkpointsOffset +} + +// PostBlock is called on every insert of finalized block (either from consensus or syncer) +// It will read any exit event that happened in block and insert it to state boltDb +func (c *checkpointManager) PostBlock(req *PostBlockRequest) error { + epoch := req.Epoch + if req.IsEpochEndingBlock { + // exit events that happened in epoch ending blocks, + // should be added to the tree of the next epoch + epoch++ + } + + // commit exit events only when we finalize a block + events, err := getExitEventsFromReceipts(epoch, req.FullBlock.Block.Number(), req.FullBlock.Receipts) + if err != nil { + return err + } + + if err := c.state.insertExitEvents(events); err != nil { + return err + } + + if c.isCheckpointBlock(req.FullBlock.Block.Header.Number, req.IsEpochEndingBlock) && + bytes.Equal(c.key.Address().Bytes(), req.FullBlock.Block.Header.Miner) { + go func(header *types.Header, epochNumber uint64) { + if err := c.submitCheckpoint(header, req.IsEpochEndingBlock); err != nil { + c.logger.Warn("failed to submit checkpoint", + "checkpoint block", header.Number, + "epoch number", epochNumber, + "error", err) + } + }(req.FullBlock.Block.Header, req.Epoch) + + c.lastSentBlock = req.FullBlock.Block.Number() + } + + return nil +} + +// BuildEventRoot returns an exit event root hash for exit tree of given epoch +func (c *checkpointManager) BuildEventRoot(epoch uint64) (types.Hash, error) { + exitEvents, err := c.state.getExitEventsByEpoch(epoch) + if err != nil { + return types.ZeroHash, err + } + + if len(exitEvents) == 0 { + return types.ZeroHash, nil + } + + tree, err := createExitTree(exitEvents) + if err != nil { + return types.ZeroHash, err + } + + return tree.Hash(), nil +} + +// GenerateExitProof generates proof of exit +func (c *checkpointManager) GenerateExitProof(exitID, epoch, checkpointBlock uint64) (types.Proof, error) { + exitEvent, err := c.state.getExitEvent(exitID, epoch) + if err != nil { + return types.Proof{}, err + } + + e, err := ExitEventABIType.Encode(exitEvent) + if err != nil { + return types.Proof{}, err + } + + exitEvents, err := c.state.getExitEventsForProof(epoch, checkpointBlock) + if err != nil { + return types.Proof{}, err + } + + tree, err := createExitTree(exitEvents) + if err != nil { + return types.Proof{}, err + } + + leafIndex, err := tree.LeafIndex(e) + if err != nil { + return types.Proof{}, err + } + + proof, err := tree.GenerateProofForLeaf(e, 0) + if err != nil { + return types.Proof{}, err + } + + return types.Proof{ + Data: proof, + Metadata: map[string]interface{}{ + "LeafIndex": leafIndex, + }, + }, nil +} + +// getExitEventsFromReceipts parses logs from receipts to find exit events +func getExitEventsFromReceipts(epoch, block uint64, receipts []*types.Receipt) ([]*ExitEvent, error) { + events := make([]*ExitEvent, 0) + + for i := 0; i < len(receipts); i++ { + for _, log := range receipts[i].Logs { + if log.Address != contracts.L2StateSenderContract { + continue + } + + event, err := decodeExitEvent(convertLog(log), epoch, block) + if err != nil { + return nil, err + } + + if event == nil { + // valid case, not an exit event + continue + } + + events = append(events, event) + } + } + + // enforce sequential order + sort.Slice(events, func(i, j int) bool { + return events[i].ID < events[j].ID + }) + + return events, nil +} diff --git a/consensus/polybft/checkpoint_manager_test.go b/consensus/polybft/checkpoint_manager_test.go new file mode 100644 index 0000000000..412c1368b8 --- /dev/null +++ b/consensus/polybft/checkpoint_manager_test.go @@ -0,0 +1,853 @@ +package polybft + +import ( + "encoding/hex" + "errors" + "math" + "math/big" + "strconv" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi/artifact" + + "github.com/umbracle/ethgo/abi" + + "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/state" + hclog "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo" + + "github.com/0xPolygon/polygon-edge/consensus/ibft/signer" + "github.com/0xPolygon/polygon-edge/consensus/polybft/bitmap" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" +) + +func TestCheckpointManager_SubmitCheckpoint(t *testing.T) { + t.Parallel() + + const ( + blocksCount = 10 + epochSize = 2 + ) + + var aliases = []string{"A", "B", "C", "D", "E"} + + validators := newTestValidatorsWithAliases(aliases) + validatorsMetadata := validators.getPublicIdentities() + txRelayerMock := newDummyTxRelayer(t) + txRelayerMock.On("Call", mock.Anything, mock.Anything, mock.Anything). + Return("2", error(nil)). + Once() + txRelayerMock.On("SendTransaction", mock.Anything, mock.Anything). + Return(ðgo.Receipt{Status: uint64(types.ReceiptSuccess)}, error(nil)). + Times(4) // send transactions for checkpoint blocks: 4, 6, 8 (pending checkpoint blocks) and 10 (latest checkpoint block) + + backendMock := new(polybftBackendMock) + backendMock.On("GetValidators", mock.Anything, mock.Anything).Return(validatorsMetadata) + + var ( + headersMap = &testHeadersMap{} + epochNumber = uint64(1) + dummyMsg = []byte("checkpoint") + idx = uint64(0) + header *types.Header + bitmap bitmap.Bitmap + signatures bls.Signatures + ) + + validators.iterAcct(aliases, func(t *testValidator) { + bitmap.Set(idx) + signatures = append(signatures, t.mustSign(dummyMsg)) + idx++ + }) + + signature, err := signatures.Aggregate().Marshal() + require.NoError(t, err) + + for i := uint64(1); i <= blocksCount; i++ { + if i%epochSize == 1 { + // epoch-beginning block + checkpoint := &CheckpointData{ + BlockRound: 0, + EpochNumber: epochNumber, + EventRoot: types.BytesToHash(generateRandomBytes(t)), + } + extra := createTestExtraObject(validatorsMetadata, validatorsMetadata, 3, 3, 3) + extra.Checkpoint = checkpoint + extra.Committed = &Signature{Bitmap: bitmap, AggregatedSignature: signature} + header = &types.Header{ + ExtraData: append(make([]byte, ExtraVanity), extra.MarshalRLPTo(nil)...), + } + epochNumber++ + } else { + header = header.Copy() + } + + header.Number = i + header.ComputeHash() + headersMap.addHeader(header) + } + + // mock blockchain + blockchainMock := new(blockchainMock) + blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headersMap.getHeader) + + validatorAcc := validators.getValidator("A") + c := &checkpointManager{ + key: wallet.NewEcdsaSigner(validatorAcc.Key()), + txRelayer: txRelayerMock, + consensusBackend: backendMock, + blockchain: blockchainMock, + logger: hclog.NewNullLogger(), + } + + err = c.submitCheckpoint(headersMap.getHeader(blocksCount), false) + require.NoError(t, err) + txRelayerMock.AssertExpectations(t) + + // make sure that expected blocks are checkpointed (epoch-ending ones) + for _, checkpointBlock := range txRelayerMock.checkpointBlocks { + header := headersMap.getHeader(checkpointBlock) + require.NotNil(t, header) + require.True(t, isEndOfPeriod(header.Number, epochSize)) + } +} + +func TestCheckpointManager_abiEncodeCheckpointBlock(t *testing.T) { + t.Parallel() + + const epochSize = uint64(10) + + currentValidators := newTestValidatorsWithAliases([]string{"A", "B", "C", "D"}) + nextValidators := newTestValidatorsWithAliases([]string{"E", "F", "G", "H"}) + header := &types.Header{Number: 50} + checkpoint := &CheckpointData{ + BlockRound: 1, + EpochNumber: getEpochNumber(t, header.Number, epochSize), + EventRoot: types.BytesToHash(generateRandomBytes(t)), + } + + proposalHash := generateRandomBytes(t) + + bmp := bitmap.Bitmap{} + i := uint64(0) + + var signatures bls.Signatures + + currentValidators.iterAcct(nil, func(v *testValidator) { + signatures = append(signatures, v.mustSign(proposalHash)) + bmp.Set(i) + i++ + }) + + aggSignature, err := signatures.Aggregate().Marshal() + require.NoError(t, err) + + extra := &Extra{Checkpoint: checkpoint} + extra.Committed = &Signature{ + AggregatedSignature: aggSignature, + Bitmap: bmp, + } + header.ExtraData = append(make([]byte, signer.IstanbulExtraVanity), extra.MarshalRLPTo(nil)...) + header.ComputeHash() + + backendMock := new(polybftBackendMock) + backendMock.On("GetValidators", mock.Anything, mock.Anything).Return(currentValidators.getPublicIdentities()) + + c := &checkpointManager{ + blockchain: &blockchainMock{}, + consensusBackend: backendMock, + logger: hclog.NewNullLogger(), + } + checkpointDataEncoded, err := c.abiEncodeCheckpointBlock(header.Number, header.Hash, extra, nextValidators.getPublicIdentities()) + require.NoError(t, err) + + submit := &contractsapi.SubmitFunction{} + require.NoError(t, submit.DecodeAbi(checkpointDataEncoded)) + + require.Equal(t, new(big.Int).SetUint64(checkpoint.EpochNumber), submit.Checkpoint.Epoch) + require.Equal(t, new(big.Int).SetUint64(header.Number), submit.Checkpoint.BlockNumber) + require.Equal(t, checkpoint.EventRoot, submit.Checkpoint.EventRoot) + require.Equal(t, new(big.Int).SetUint64(checkpoint.BlockRound), submit.CheckpointMetadata.BlockRound) +} + +func TestCheckpointManager_getCurrentCheckpointID(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + checkpointID string + returnError error + errSubstring string + }{ + { + name: "Happy path", + checkpointID: "16", + returnError: error(nil), + errSubstring: "", + }, + { + name: "Rootchain call returns an error", + checkpointID: "", + returnError: errors.New("internal error"), + errSubstring: "failed to invoke currentCheckpointId function on the rootchain", + }, + { + name: "Failed to parse return value from rootchain", + checkpointID: "Hello World!", + returnError: error(nil), + errSubstring: "failed to convert current checkpoint id", + }, + } + + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + txRelayerMock := newDummyTxRelayer(t) + txRelayerMock.On("Call", mock.Anything, mock.Anything, mock.Anything). + Return(c.checkpointID, c.returnError). + Once() + + checkpointMgr := &checkpointManager{ + txRelayer: txRelayerMock, + key: wallet.GenerateAccount().Ecdsa, + logger: hclog.NewNullLogger(), + } + actualCheckpointID, err := checkpointMgr.getLatestCheckpointBlock() + if c.errSubstring == "" { + expectedCheckpointID, err := strconv.ParseUint(c.checkpointID, 0, 64) + require.NoError(t, err) + require.Equal(t, expectedCheckpointID, actualCheckpointID) + } else { + require.ErrorContains(t, err, c.errSubstring) + } + + txRelayerMock.AssertExpectations(t) + }) + } +} + +func TestCheckpointManager_IsCheckpointBlock(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + blockNumber uint64 + checkpointsOffset uint64 + isEpochEndingBlock bool + isCheckpointBlock bool + }{ + { + name: "Not checkpoint block", + blockNumber: 3, + checkpointsOffset: 6, + isEpochEndingBlock: false, + isCheckpointBlock: false, + }, + { + name: "Checkpoint block", + blockNumber: 6, + checkpointsOffset: 6, + isEpochEndingBlock: false, + isCheckpointBlock: true, + }, + { + name: "Epoch ending block - Fixed epoch size met", + blockNumber: 10, + checkpointsOffset: 5, + isEpochEndingBlock: true, + isCheckpointBlock: true, + }, + { + name: "Epoch ending block - Epoch ended before fix size was met", + blockNumber: 9, + checkpointsOffset: 5, + isEpochEndingBlock: true, + isCheckpointBlock: true, + }, + } + + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + checkpointMgr := newCheckpointManager(wallet.NewEcdsaSigner(createTestKey(t)), c.checkpointsOffset, types.ZeroAddress, nil, nil, nil, hclog.NewNullLogger(), nil) + require.Equal(t, c.isCheckpointBlock, checkpointMgr.isCheckpointBlock(c.blockNumber, c.isEpochEndingBlock)) + }) + } +} + +func TestCheckpointManager_PostBlock(t *testing.T) { + const ( + numOfReceipts = 5 + block = 5 + epoch = 1 + ) + + state := newTestState(t) + + receipts := make([]*types.Receipt, numOfReceipts) + for i := 0; i < numOfReceipts; i++ { + receipts[i] = &types.Receipt{Logs: []*types.Log{ + createTestLogForExitEvent(t, uint64(i)), + }} + } + + req := &PostBlockRequest{FullBlock: &types.FullBlock{Block: &types.Block{Header: &types.Header{Number: block}}, Receipts: receipts}, + Epoch: epoch} + + checkpointManager := newCheckpointManager(wallet.NewEcdsaSigner(createTestKey(t)), 5, types.ZeroAddress, + nil, nil, nil, hclog.NewNullLogger(), state) + + t.Run("PostBlock - not epoch ending block", func(t *testing.T) { + req.IsEpochEndingBlock = false + require.NoError(t, checkpointManager.PostBlock(req)) + + exitEvents, err := state.getExitEvents(epoch, func(exitEvent *ExitEvent) bool { + return exitEvent.BlockNumber == block + }) + + require.NoError(t, err) + require.Len(t, exitEvents, numOfReceipts) + require.Equal(t, uint64(epoch), exitEvents[0].EpochNumber) + }) + + t.Run("PostBlock - epoch ending block (exit events are saved to the next epoch)", func(t *testing.T) { + req.IsEpochEndingBlock = true + require.NoError(t, checkpointManager.PostBlock(req)) + + exitEvents, err := state.getExitEvents(epoch+1, func(exitEvent *ExitEvent) bool { + return exitEvent.BlockNumber == block + }) + + require.NoError(t, err) + require.Len(t, exitEvents, numOfReceipts) + require.Equal(t, uint64(epoch+1), exitEvents[0].EpochNumber) + }) +} + +func TestCheckpointManager_BuildEventRoot(t *testing.T) { + t.Parallel() + + const ( + numOfBlocks = 10 + numOfEventsPerBlock = 2 + ) + + state := newTestState(t) + checkpointManager := &checkpointManager{state: state} + + encodedEvents := setupExitEventsForProofVerification(t, state, numOfBlocks, numOfEventsPerBlock) + + t.Run("Get exit event root hash", func(t *testing.T) { + t.Parallel() + + tree, err := NewMerkleTree(encodedEvents) + require.NoError(t, err) + + hash, err := checkpointManager.BuildEventRoot(1) + require.NoError(t, err) + require.Equal(t, tree.Hash(), hash) + }) + + t.Run("Get exit event root hash - no events", func(t *testing.T) { + t.Parallel() + + hash, err := checkpointManager.BuildEventRoot(2) + require.NoError(t, err) + require.Equal(t, types.Hash{}, hash) + }) +} + +func TestCheckpointManager_GenerateExitProof(t *testing.T) { + t.Parallel() + + const ( + numOfBlocks = 10 + numOfEventsPerBlock = 2 + ) + + state := newTestState(t) + checkpointManager := &checkpointManager{ + state: state, + } + + encodedEvents := setupExitEventsForProofVerification(t, state, numOfBlocks, numOfEventsPerBlock) + checkpointEvents := encodedEvents[:numOfEventsPerBlock] + + // manually create merkle tree for a desired checkpoint to verify the generated proof + tree, err := NewMerkleTree(checkpointEvents) + require.NoError(t, err) + + proof, err := checkpointManager.GenerateExitProof(1, 1, 1) + require.NoError(t, err) + require.NotNil(t, proof) + + t.Run("Generate and validate exit proof", func(t *testing.T) { + t.Parallel() + // verify generated proof on desired tree + require.NoError(t, VerifyProof(1, encodedEvents[1], proof.Data, tree.Hash())) + }) + + t.Run("Generate and validate exit proof - invalid proof", func(t *testing.T) { + t.Parallel() + + // copy and make proof invalid + invalidProof := make([]types.Hash, len(proof.Data)) + copy(invalidProof, proof.Data) + invalidProof[0][0]++ + + // verify generated proof on desired tree + require.ErrorContains(t, VerifyProof(1, encodedEvents[1], invalidProof, tree.Hash()), "not a member of merkle tree") + }) + + t.Run("Generate exit proof - no event", func(t *testing.T) { + t.Parallel() + + _, err := checkpointManager.GenerateExitProof(21, 1, 1) + require.ErrorContains(t, err, "could not find any exit event that has an id") + }) +} + +func TestPerformExit(t *testing.T) { + t.Parallel() + + //create validator set + currentValidators := newTestValidatorsWithAliases([]string{"A", "B", "C", "D"}, []uint64{100, 100, 100, 100}) + accSet := currentValidators.getPublicIdentities() + + senderAddress := types.Address{1} + bn256Addr := types.Address{2} + l1Cntract := types.Address{3} + + alloc := map[types.Address]*chain.GenesisAccount{ + senderAddress: { + Balance: big.NewInt(100000000000), + }, + contracts.BLSContract: { + Code: contractsapi.BLS.DeployedBytecode, + }, + bn256Addr: { + Code: contractsapi.BLS256.DeployedBytecode, + }, + l1Cntract: { + Code: contractsapi.TestL1StateReceiver.DeployedBytecode, + }, + } + transition := newTestTransition(t, alloc) + + getField := func(addr types.Address, abi *abi.ABI, function string, args ...interface{}) []byte { + input, err := abi.GetMethod(function).Encode(args) + require.NoError(t, err) + + result := transition.Call2(senderAddress, addr, input, big.NewInt(0), 1000000000) + require.NoError(t, result.Err) + require.True(t, result.Succeeded()) + require.False(t, result.Failed()) + + return result.ReturnValue + } + + rootchainContractAddress := deployRootchainContract(t, transition, contractsapi.CheckpointManager, senderAddress, accSet, bn256Addr) + exitHelperContractAddress := deployExitContract(t, transition, contractsapi.ExitHelper, senderAddress, rootchainContractAddress) + + require.Equal(t, getField(rootchainContractAddress, contractsapi.CheckpointManager.Abi, "currentCheckpointBlockNumber")[31], uint8(0)) + + cm := checkpointManager{ + blockchain: &blockchainMock{}, + } + accSetHash, err := accSet.Hash() + require.NoError(t, err) + + blockHash := types.Hash{5} + blockNumber := uint64(1) + epochNumber := uint64(1) + blockRound := uint64(1) + + exits := []*ExitEvent{ + { + ID: 1, + Sender: ethgo.Address{7}, + Receiver: ethgo.Address(l1Cntract), + Data: []byte{123}, + }, + { + ID: 2, + Sender: ethgo.Address{7}, + Receiver: ethgo.Address(l1Cntract), + Data: []byte{21}, + }, + } + exitTrie, err := createExitTree(exits) + require.NoError(t, err) + + eventRoot := exitTrie.Hash() + + checkpointData := CheckpointData{ + BlockRound: blockRound, + EpochNumber: epochNumber, + CurrentValidatorsHash: accSetHash, + NextValidatorsHash: accSetHash, + EventRoot: eventRoot, + } + + checkpointHash, err := checkpointData.Hash( + cm.blockchain.GetChainID(), + blockRound, + blockHash) + require.NoError(t, err) + + bmp := bitmap.Bitmap{} + i := uint64(0) + + var signatures bls.Signatures + + currentValidators.iterAcct(nil, func(v *testValidator) { + signatures = append(signatures, v.mustSign(checkpointHash[:])) + bmp.Set(i) + i++ + }) + + aggSignature, err := signatures.Aggregate().Marshal() + require.NoError(t, err) + + extra := &Extra{ + Checkpoint: &checkpointData, + } + extra.Committed = &Signature{ + AggregatedSignature: aggSignature, + Bitmap: bmp, + } + + submitCheckpointEncoded, err := cm.abiEncodeCheckpointBlock( + blockNumber, + blockHash, + extra, + accSet) + require.NoError(t, err) + + result := transition.Call2(senderAddress, rootchainContractAddress, submitCheckpointEncoded, big.NewInt(0), 1000000000) + require.NoError(t, result.Err) + require.True(t, result.Succeeded()) + require.False(t, result.Failed()) + require.Equal(t, getField(rootchainContractAddress, contractsapi.CheckpointManager.Abi, "currentCheckpointBlockNumber")[31], uint8(1)) + + //check that the exit havent performed + res := getField(exitHelperContractAddress, contractsapi.ExitHelper.Abi, "processedExits", exits[0].ID) + require.Equal(t, int(res[31]), 0) + + proofExitEvent, err := ExitEventABIType.Encode(exits[0]) + require.NoError(t, err) + proof, err := exitTrie.GenerateProofForLeaf(proofExitEvent, 0) + require.NoError(t, err) + leafIndex, err := exitTrie.LeafIndex(proofExitEvent) + require.NoError(t, err) + + ehExit, err := contractsapi.ExitHelper.Abi.GetMethod("exit").Encode([]interface{}{ + blockNumber, + leafIndex, + proofExitEvent, + proof, + }) + require.NoError(t, err) + + result = transition.Call2(senderAddress, exitHelperContractAddress, ehExit, big.NewInt(0), 1000000000) + require.NoError(t, result.Err) + require.True(t, result.Succeeded()) + require.False(t, result.Failed()) + + //check true + res = getField(exitHelperContractAddress, contractsapi.ExitHelper.Abi, "processedExits", exits[0].ID) + require.Equal(t, int(res[31]), 1) + + lastID := getField(l1Cntract, contractsapi.TestL1StateReceiver.Abi, "id") + require.Equal(t, lastID[31], uint8(1)) + + lastAddr := getField(l1Cntract, contractsapi.TestL1StateReceiver.Abi, "addr") + require.Equal(t, exits[0].Sender[:], lastAddr[12:]) + + lastCounter := getField(l1Cntract, contractsapi.TestL1StateReceiver.Abi, "counter") + require.Equal(t, lastCounter[31], uint8(1)) +} + +func TestCommitEpoch(t *testing.T) { + // init validator sets + validatorSetSize := []int{5, 10, 50, 100, 150, 200} + // number of delegators per validator + delegPerVal := 100 + + intialBalance := uint64(5 * math.Pow(10, 18)) // 5 tokens + reward := uint64(math.Pow(10, 18)) // 1 token + delegateAmount := uint64(math.Pow(10, 18)) / 2 // 0.5 token + + validatorSets := make([]*testValidators, len(validatorSetSize), len(validatorSetSize)) + + // create all validator sets which will be used in test + for i, size := range validatorSetSize { + aliasses := make([]string, size, size) + vps := make([]uint64, size, size) + + for j := 0; j < size; j++ { + aliasses[j] = "v" + strconv.Itoa(j) + vps[j] = intialBalance + } + + validatorSets[i] = newTestValidatorsWithAliases(aliasses, vps) + } + + // iterate through the validator set and do the test for each of them + for _, currentValidators := range validatorSets { + accSet := currentValidators.getPublicIdentities() + valid2deleg := make(map[types.Address][]*wallet.Key, accSet.Len()) // delegators assigned to validators + + // add contracts to genesis data + alloc := map[types.Address]*chain.GenesisAccount{ + contracts.ValidatorSetContract: { + Code: contractsapi.ChildValidatorSet.DeployedBytecode, + }, + contracts.BLSContract: { + Code: contractsapi.BLS.DeployedBytecode, + }, + } + + // validator data for polybft config + initValidators := make([]*Validator, accSet.Len()) + + for i, validator := range accSet { + // add validator to genesis data + alloc[validator.Address] = &chain.GenesisAccount{ + Balance: validator.VotingPower, + } + + // create validator data for polybft config + initValidators[i] = &Validator{ + Address: validator.Address, + Balance: validator.VotingPower, + BlsKey: hex.EncodeToString(validator.BlsKey.Marshal()), + } + + // create delegators + delegWallets := createRandomTestKeys(t, delegPerVal) + + // add delegators to genesis data + for j := 0; j < delegPerVal; j++ { + delegator := delegWallets[j] + alloc[types.Address(delegator.Address())] = &chain.GenesisAccount{ + Balance: new(big.Int).SetUint64(intialBalance), + } + } + + valid2deleg[validator.Address] = delegWallets + } + + transition := newTestTransition(t, alloc) + + polyBFTConfig := PolyBFTConfig{ + InitialValidatorSet: initValidators, + BlockTime: 2 * time.Second, + EpochSize: 24 * 60 * 60 / 2, + SprintSize: 5, + EpochReward: reward, + // use 1st account as governance address + Governance: currentValidators.toValidatorSet().validators.GetAddresses()[0], + ValidatorSetAddr: contracts.ValidatorSetContract, + } + + // get data for ChildValidatorSet initialization + initInput, err := getInitChildValidatorSetInput(polyBFTConfig) + require.NoError(t, err) + + // init ChildValidatorSet + err = initContract(contracts.ValidatorSetContract, initInput, "ChildValidatorSet", transition) + require.NoError(t, err) + + // delegate amounts to validators + for valAddress, delegators := range valid2deleg { + for _, delegator := range delegators { + encoded, err := contractsapi.ChildValidatorSet.Abi.Methods["delegate"].Encode( + []interface{}{valAddress, false}) + + require.NoError(t, err) + + result := transition.Call2(types.Address(delegator.Address()), contracts.ValidatorSetContract, encoded, new(big.Int).SetUint64(delegateAmount), 1000000000000) + require.False(t, result.Failed()) + } + } + + // create input for commit epoch + commitEpoch := createCommitEpoch(t, 1, accSet, polyBFTConfig.EpochSize) + input, err := commitEpoch.EncodeAbi() + require.NoError(t, err) + + // call commit epoch + result := transition.Call2(contracts.SystemCaller, contracts.ValidatorSetContract, input, big.NewInt(0), 10000000000) + + t.Logf("Number of validators %d when we add %d of delegators, Gas used %+v\n", accSet.Len(), accSet.Len()*delegPerVal, result.GasUsed) + + commitEpoch = createCommitEpoch(t, 2, accSet, polyBFTConfig.EpochSize) + input, err = commitEpoch.EncodeAbi() + require.NoError(t, err) + + // call commit epoch + result = transition.Call2(contracts.SystemCaller, contracts.ValidatorSetContract, input, big.NewInt(0), 10000000000) + t.Logf("Number of validators %d, Number of delegator %d, Gas used %+v\n", accSet.Len(), accSet.Len()*delegPerVal, result.GasUsed) + } +} + +func createCommitEpoch(t *testing.T, epochID uint64, validatorSet AccountSet, epochSize uint64) *contractsapi.CommitEpochFunction { + t.Helper() + + var startBlock uint64 = 0 + if epochID > 1 { + startBlock = (epochID - 1) * epochSize + } + + uptime := &contractsapi.Uptime{ + EpochID: new(big.Int).SetUint64(epochID), + UptimeData: []*contractsapi.UptimeData{}, + TotalBlocks: new(big.Int).SetUint64(epochSize), + } + + commitEpoch := &contractsapi.CommitEpochFunction{ + ID: uptime.EpochID, + Epoch: &contractsapi.Epoch{ + StartBlock: new(big.Int).SetUint64(startBlock + 1), + EndBlock: new(big.Int).SetUint64(epochSize * epochID), + EpochRoot: types.Hash{}, + }, + Uptime: uptime, + } + + for i := range validatorSet { + uptime.AddValidatorUptime(validatorSet[i].Address, int64(epochSize)) + } + + return commitEpoch +} + +func deployRootchainContract(t *testing.T, transition *state.Transition, rootchainArtifact *artifact.Artifact, sender types.Address, accSet AccountSet, bn256Addr types.Address) types.Address { + t.Helper() + + result := transition.Create2(sender, rootchainArtifact.Bytecode, big.NewInt(0), 1000000000) + assert.NoError(t, result.Err) + rcAddress := result.Address + + initialize := contractsapi.InitializeCheckpointManagerFunction{ + NewBls: contracts.BLSContract, + NewBn256G2: bn256Addr, + NewDomain: types.BytesToHash(bls.GetDomain()), + NewValidatorSet: accSet.ToAPIBinding(), + } + + init, err := initialize.EncodeAbi() + if err != nil { + t.Fatal(err) + } + + result = transition.Call2(sender, rcAddress, init, big.NewInt(0), 1000000000) + require.True(t, result.Succeeded()) + require.False(t, result.Failed()) + require.NoError(t, result.Err) + + getDomain, err := rootchainArtifact.Abi.GetMethod("domain").Encode([]interface{}{}) + require.NoError(t, err) + + result = transition.Call2(sender, rcAddress, getDomain, big.NewInt(0), 1000000000) + require.Equal(t, result.ReturnValue, bls.GetDomain()) + + return rcAddress +} + +func deployExitContract(t *testing.T, transition *state.Transition, exitHelperArtifcat *artifact.Artifact, sender types.Address, rootchainContractAddress types.Address) types.Address { + t.Helper() + + result := transition.Create2(sender, exitHelperArtifcat.Bytecode, big.NewInt(0), 1000000000) + assert.NoError(t, result.Err) + ehAddress := result.Address + + ehInit, err := exitHelperArtifcat.Abi.GetMethod("initialize").Encode([]interface{}{ethgo.Address(rootchainContractAddress)}) + require.NoError(t, err) + + result = transition.Call2(sender, ehAddress, ehInit, big.NewInt(0), 1000000000) + require.NoError(t, result.Err) + require.True(t, result.Succeeded()) + require.False(t, result.Failed()) + + return ehAddress +} + +var _ txrelayer.TxRelayer = (*dummyTxRelayer)(nil) + +type dummyTxRelayer struct { + mock.Mock + + test *testing.T + checkpointBlocks []uint64 +} + +func newDummyTxRelayer(t *testing.T) *dummyTxRelayer { + t.Helper() + + return &dummyTxRelayer{test: t} +} + +func (d dummyTxRelayer) Call(from ethgo.Address, to ethgo.Address, input []byte) (string, error) { + args := d.Called(from, to, input) + + return args.String(0), args.Error(1) +} + +func (d *dummyTxRelayer) SendTransaction(transaction *ethgo.Transaction, key ethgo.Key) (*ethgo.Receipt, error) { + blockNumber := getBlockNumberCheckpointSubmitInput(d.test, transaction.Input) + d.checkpointBlocks = append(d.checkpointBlocks, blockNumber) + args := d.Called(transaction, key) + + return args.Get(0).(*ethgo.Receipt), args.Error(1) //nolint:forcetypeassert +} + +// SendTransactionLocal sends non-signed transaction (this is only for testing purposes) +func (d *dummyTxRelayer) SendTransactionLocal(txn *ethgo.Transaction) (*ethgo.Receipt, error) { + args := d.Called(txn) + + return args.Get(0).(*ethgo.Receipt), args.Error(1) //nolint:forcetypeassert +} + +func getBlockNumberCheckpointSubmitInput(t *testing.T, input []byte) uint64 { + t.Helper() + + submit := &contractsapi.SubmitFunction{} + require.NoError(t, submit.DecodeAbi(input)) + + return submit.Checkpoint.BlockNumber.Uint64() +} + +func createTestLogForExitEvent(t *testing.T, exitEventID uint64) *types.Log { + t.Helper() + + topics := make([]types.Hash, 4) + topics[0] = types.Hash(exitEventABI.ID()) + topics[1] = types.BytesToHash(itob(exitEventID)) + topics[2] = types.BytesToHash(types.StringToAddress("0x1111").Bytes()) + topics[3] = types.BytesToHash(types.StringToAddress("0x2222").Bytes()) + someType := abi.MustNewType("tuple(string firstName, string lastName)") + encodedData, err := someType.Encode(map[string]string{"firstName": "John", "lastName": "Doe"}) + require.NoError(t, err) + + return &types.Log{ + Address: contracts.L2StateSenderContract, + Topics: topics, + Data: encodedData, + } +} diff --git a/consensus/polybft/consensus_metrics.go b/consensus/polybft/consensus_metrics.go new file mode 100644 index 0000000000..88d14ca097 --- /dev/null +++ b/consensus/polybft/consensus_metrics.go @@ -0,0 +1,46 @@ +package polybft + +import ( + "time" + + "github.com/0xPolygon/polygon-edge/types" + "github.com/armon/go-metrics" +) + +const ( + // consensusMetricsPrefix is a consensus-related metrics prefix + consensusMetricsPrefix = "consensus" +) + +// updateBlockMetrics updates various metrics based on the given block +// (such as block interval, number of transactions and block rounds metrics) +func updateBlockMetrics(currentBlock *types.Block, parentHeader *types.Header) error { + if currentBlock.Number() > 1 { + parentTime := time.Unix(int64(parentHeader.Timestamp), 0) + headerTime := time.Unix(int64(currentBlock.Header.Timestamp), 0) + // update the block interval metric + metrics.SetGauge([]string{consensusMetricsPrefix, "block_interval"}, float32(headerTime.Sub(parentTime).Seconds())) + } + + // update the number of transactions in the block metric + metrics.SetGauge([]string{consensusMetricsPrefix, "num_txs"}, float32(len(currentBlock.Body().Transactions))) + + extra, err := GetIbftExtra(currentBlock.Header.ExtraData) + if err != nil { + return err + } + + // number of rounds needed to seal a block + metrics.SetGauge([]string{consensusMetricsPrefix, "rounds"}, float32(extra.Checkpoint.BlockRound)) + + return nil +} + +// updateEpochMetrics updates epoch-related metrics +// (e.g. epoch number, validator set length) +func updateEpochMetrics(epoch epochMetadata) { + // update epoch number metrics + metrics.SetGauge([]string{consensusMetricsPrefix, "epoch_number"}, float32(epoch.Number)) + // update number of validators metrics + metrics.SetGauge([]string{consensusMetricsPrefix, "validators"}, float32(epoch.Validators.Len())) +} diff --git a/consensus/polybft/consensus_runtime.go b/consensus/polybft/consensus_runtime.go new file mode 100644 index 0000000000..165f21c93e --- /dev/null +++ b/consensus/polybft/consensus_runtime.go @@ -0,0 +1,987 @@ +package polybft + +import ( + "bytes" + "errors" + "fmt" + "math/big" + "sort" + "sync" + "sync/atomic" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" + + "github.com/0xPolygon/go-ibft/messages" + "github.com/0xPolygon/go-ibft/messages/proto" + hcf "github.com/hashicorp/go-hclog" +) + +const ( + maxCommitmentSize = 10 + stateFileName = "consensusState.db" + uptimeLookbackSize = 2 // number of blocks to calculate uptime from the previous epoch +) + +var ( + // errNotAValidator represents "node is not a validator" error message + errNotAValidator = errors.New("node is not a validator") + // errQuorumNotReached represents "quorum not reached for commitment message" error message + errQuorumNotReached = errors.New("quorum not reached for commitment message") +) + +// txPoolInterface is an abstraction of transaction pool +type txPoolInterface interface { + Prepare() + Length() uint64 + Peek() *types.Transaction + Pop(*types.Transaction) + Drop(*types.Transaction) + Demote(*types.Transaction) + SetSealing(bool) + ResetWithHeaders(...*types.Header) +} + +// epochMetadata is the static info for epoch currently being processed +type epochMetadata struct { + // Number is the number of the epoch + Number uint64 + + FirstBlockInEpoch uint64 + + // Validators is the set of validators for the epoch + Validators AccountSet +} + +type guardedDataDTO struct { + // last built block header at the time of collecting data + lastBuiltBlock *types.Header + + // epoch metadata at the time of collecting data + epoch *epochMetadata + + // proposerSnapshot at the time of collecting data + proposerSnapshot *ProposerSnapshot +} + +// runtimeConfig is a struct that holds configuration data for given consensus runtime +type runtimeConfig struct { + PolyBFTConfig *PolyBFTConfig + DataDir string + Key *wallet.Key + State *State + blockchain blockchainBackend + polybftBackend polybftBackend + txPool txPoolInterface + bridgeTopic topic +} + +// consensusRuntime is a struct that provides consensus runtime features like epoch, state and event management +type consensusRuntime struct { + // config represents wrapper around required parameters which are received from the outside + config *runtimeConfig + + // state is reference to the struct which encapsulates bridge events persistence logic + state *State + + // fsm instance which is created for each `runSequence` + fsm *fsm + + // lock is a lock to access 'epoch' and `lastBuiltBlock` + lock sync.RWMutex + + // epoch is the metadata for the current epoch + epoch *epochMetadata + + // lastBuiltBlock is the header of the last processed block + lastBuiltBlock *types.Header + + // activeValidatorFlag indicates whether the given node is amongst currently active validator set + activeValidatorFlag uint32 + + // checkpointManager represents abstraction for checkpoint submission + checkpointManager CheckpointManager + + // proposerCalculator is the object which manipulates with ProposerSnapshot + proposerCalculator *ProposerCalculator + + // manager for state sync bridge transactions + stateSyncManager StateSyncManager + + // logger instance + logger hcf.Logger +} + +// newConsensusRuntime creates and starts a new consensus runtime instance with event tracking +func newConsensusRuntime(log hcf.Logger, config *runtimeConfig) (*consensusRuntime, error) { + proposerCalculator, err := NewProposerCalculator(config, log.Named("proposer_calculator")) + if err != nil { + return nil, fmt.Errorf("failed to create consensus runtime, error while creating proposer calculator %w", err) + } + + runtime := &consensusRuntime{ + state: config.State, + config: config, + lastBuiltBlock: config.blockchain.CurrentHeader(), + proposerCalculator: proposerCalculator, + logger: log.Named("consensus_runtime"), + } + + if err := runtime.initStateSyncManager(log); err != nil { + return nil, err + } + + if err := runtime.initCheckpointManager(log); err != nil { + return nil, err + } + + // we need to call restart epoch on runtime to initialize epoch state + runtime.epoch, err = runtime.restartEpoch(runtime.lastBuiltBlock) + if err != nil { + return nil, fmt.Errorf("consensus runtime creation - restart epoch failed: %w", err) + } + + return runtime, nil +} + +// close is used to tear down allocated resources +func (c *consensusRuntime) close() { + c.stateSyncManager.Close() +} + +// initStateSyncManager initializes state sync manager +// if bridge is not enabled, then a dummy state sync manager will be used +func (c *consensusRuntime) initStateSyncManager(logger hcf.Logger) error { + if c.IsBridgeEnabled() { + stateSyncManager, err := NewStateSyncManager( + logger, + c.config.State, + &stateSyncConfig{ + key: c.config.Key, + stateSenderAddr: c.config.PolyBFTConfig.Bridge.BridgeAddr, + jsonrpcAddr: c.config.PolyBFTConfig.Bridge.JSONRPCEndpoint, + dataDir: c.config.DataDir, + topic: c.config.bridgeTopic, + maxCommitmentSize: maxCommitmentSize, + }, + ) + + if err != nil { + return err + } + + c.stateSyncManager = stateSyncManager + } else { + c.stateSyncManager = &dummyStateSyncManager{} + } + + return c.stateSyncManager.Init() +} + +// initCheckpointManager initializes checkpoint manager +// if bridge is not enabled, then a dummy checkpoint manager will be used +func (c *consensusRuntime) initCheckpointManager(logger hcf.Logger) error { + if c.IsBridgeEnabled() { + // enable checkpoint manager + txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(c.config.PolyBFTConfig.Bridge.JSONRPCEndpoint)) + if err != nil { + return err + } + + c.checkpointManager = newCheckpointManager( + wallet.NewEcdsaSigner(c.config.Key), + defaultCheckpointsOffset, + c.config.PolyBFTConfig.Bridge.CheckpointAddr, + txRelayer, + c.config.blockchain, + c.config.polybftBackend, + logger.Named("checkpoint_manager"), + c.state) + } else { + c.checkpointManager = &dummyCheckpointManager{} + } + + return nil +} + +// getGuardedData returns last build block, proposer snapshot and current epochMetadata in a thread-safe manner. +func (c *consensusRuntime) getGuardedData() (guardedDataDTO, error) { + c.lock.RLock() + defer c.lock.RUnlock() + + lastBuiltBlock := c.lastBuiltBlock.Copy() + epoch := new(epochMetadata) + *epoch = *c.epoch // shallow copy, don't need to make validators copy because AccountSet is immutable + proposerSnapshot, ok := c.proposerCalculator.GetSnapshot() + + if !ok { + return guardedDataDTO{}, errors.New("cannot collect shared data, snapshot is empty") + } + + return guardedDataDTO{ + epoch: epoch, + lastBuiltBlock: lastBuiltBlock, + proposerSnapshot: proposerSnapshot, + }, nil +} + +func (c *consensusRuntime) IsBridgeEnabled() bool { + return c.config.PolyBFTConfig.IsBridgeEnabled() +} + +// OnBlockInserted is called whenever fsm or syncer inserts new block +func (c *consensusRuntime) OnBlockInserted(fullBlock *types.FullBlock) { + c.lock.Lock() + defer c.lock.Unlock() + + if c.lastBuiltBlock != nil && c.lastBuiltBlock.Number >= fullBlock.Block.Number() { + c.logger.Debug("on block inserted already handled", + "current", c.lastBuiltBlock.Number, "block", fullBlock.Block.Number()) + + return + } + + if err := updateBlockMetrics(fullBlock.Block, c.lastBuiltBlock); err != nil { + c.logger.Error("failed to update block metrics", "error", err) + } + + // after the block has been written we reset the txpool so that the old transactions are removed + c.config.txPool.ResetWithHeaders(fullBlock.Block.Header) + + var ( + epoch = c.epoch + err error + // TODO - this will need to take inconsideration if slashing occurred + isEndOfEpoch = c.isFixedSizeOfEpochMet(fullBlock.Block.Header.Number, epoch) + ) + + postBlock := &PostBlockRequest{FullBlock: fullBlock, Epoch: epoch.Number, IsEpochEndingBlock: isEndOfEpoch} + + // handle commitment and proofs creation + if err := c.stateSyncManager.PostBlock(postBlock); err != nil { + c.logger.Error("failed to post block state sync", "err", err) + } + + // handle exit events that happened in block + if err := c.checkpointManager.PostBlock(postBlock); err != nil { + c.logger.Error("failed to post block in checkpoint manager", "err", err) + } + + // update proposer priorities + if err := c.proposerCalculator.PostBlock(postBlock); err != nil { + c.logger.Error("Could not update proposer calculator", "err", err) + } + + if isEndOfEpoch { + if epoch, err = c.restartEpoch(fullBlock.Block.Header); err != nil { + c.logger.Error("failed to restart epoch after block inserted", "error", err) + + return + } + } + + // finally update runtime state (lastBuiltBlock, epoch, proposerSnapshot) + c.epoch = epoch + c.lastBuiltBlock = fullBlock.Block.Header +} + +// FSM creates a new instance of fsm +func (c *consensusRuntime) FSM() error { + sharedData, err := c.getGuardedData() + if err != nil { + return fmt.Errorf("cannot create fsm: %w", err) + } + + parent, epoch, proposerSnapshot := sharedData.lastBuiltBlock, sharedData.epoch, sharedData.proposerSnapshot + + if !epoch.Validators.ContainsNodeID(c.config.Key.String()) { + return errNotAValidator + } + + blockBuilder, err := c.config.blockchain.NewBlockBuilder( + parent, + types.Address(c.config.Key.Address()), + c.config.txPool, + c.config.PolyBFTConfig.BlockTime, + c.logger, + ) + + if err != nil { + return fmt.Errorf("cannot create block builder for fsm: %w", err) + } + + // TODO - recognize slashing occurred + slash := false + + pendingBlockNumber := parent.Number + 1 + isEndOfSprint := slash || c.isFixedSizeOfSprintMet(pendingBlockNumber, epoch) + isEndOfEpoch := slash || c.isFixedSizeOfEpochMet(pendingBlockNumber, epoch) + + valSet := NewValidatorSet(epoch.Validators, c.logger) + + exitRootHash, err := c.checkpointManager.BuildEventRoot(epoch.Number) + if err != nil { + return fmt.Errorf("could not build exit root hash for fsm: %w", err) + } + + ff := &fsm{ + config: c.config.PolyBFTConfig, + parent: parent, + backend: c.config.blockchain, + polybftBackend: c.config.polybftBackend, + exitEventRootHash: exitRootHash, + epochNumber: epoch.Number, + blockBuilder: blockBuilder, + validators: valSet, + isEndOfEpoch: isEndOfEpoch, + isEndOfSprint: isEndOfSprint, + proposerSnapshot: proposerSnapshot, + logger: c.logger.Named("fsm"), + } + + if isEndOfSprint { + commitment, err := c.stateSyncManager.Commitment() + if err != nil { + return err + } + + ff.proposerCommitmentToRegister = commitment + } + + if isEndOfEpoch { + ff.uptimeCounter, err = c.calculateUptime(parent, epoch) + if err != nil { + return fmt.Errorf("cannot calculate uptime: %w", err) + } + } + + c.logger.Info( + "[FSM built]", + "epoch", epoch.Number, + "endOfEpoch", isEndOfEpoch, + "endOfSprint", isEndOfSprint, + ) + + c.lock.Lock() + c.fsm = ff + c.lock.Unlock() + + return nil +} + +// restartEpoch resets the previously run epoch and moves to the next one +// returns *epochMetadata different from nil if the lastEpoch is not the current one and everything was successful +func (c *consensusRuntime) restartEpoch(header *types.Header) (*epochMetadata, error) { + lastEpoch := c.epoch + + systemState, err := c.getSystemState(header) + if err != nil { + return nil, err + } + + epochNumber, err := systemState.GetEpoch() + if err != nil { + return nil, err + } + + if lastEpoch != nil { + // Epoch might be already in memory, if its the same number do nothing -> just return provided last one + // Otherwise, reset the epoch metadata and restart the async services + if lastEpoch.Number == epochNumber { + return lastEpoch, nil + } + } + + validatorSet, err := c.config.polybftBackend.GetValidators(header.Number, nil) + if err != nil { + return nil, fmt.Errorf("restart epoch - cannot get validators: %w", err) + } + + updateEpochMetrics(epochMetadata{ + Number: epochNumber, + Validators: validatorSet, + }) + + firstBlockInEpoch, err := c.getFirstBlockOfEpoch(epochNumber, header) + if err != nil { + return nil, err + } + + if err := c.state.cleanEpochsFromDB(); err != nil { + c.logger.Error("Could not clean previous epochs from db.", "error", err) + } + + if err := c.state.insertEpoch(epochNumber); err != nil { + return nil, fmt.Errorf("an error occurred while inserting new epoch in db. Reason: %w", err) + } + + c.logger.Info( + "restartEpoch", + "block number", header.Number, + "epoch", epochNumber, + "validators", validatorSet.Len(), + "firstBlockInEpoch", firstBlockInEpoch, + ) + + reqObj := &PostEpochRequest{ + SystemState: systemState, + NewEpochID: epochNumber, + FirstBlockOfEpoch: firstBlockInEpoch, + ValidatorSet: NewValidatorSet(validatorSet, c.logger), + } + + if err := c.stateSyncManager.PostEpoch(reqObj); err != nil { + return nil, err + } + + return &epochMetadata{ + Number: epochNumber, + Validators: validatorSet, + FirstBlockInEpoch: firstBlockInEpoch, + }, nil +} + +// calculateUptime calculates uptime for blocks starting from the last built block in current epoch, +// and ending at the last block of previous epoch +func (c *consensusRuntime) calculateUptime(currentBlock *types.Header, + epoch *epochMetadata) (*contractsapi.CommitEpochFunction, error) { + uptimeCounter := map[types.Address]int64{} + blockHeader := currentBlock + epochID := epoch.Number + totalBlocks := int64(0) + + getSealersForBlock := func(blockExtra *Extra, validators AccountSet) error { + signers, err := validators.GetFilteredValidators(blockExtra.Parent.Bitmap) + if err != nil { + return err + } + + totalBlocks++ + + for _, a := range signers.GetAddresses() { + uptimeCounter[a]++ + } + + return nil + } + + blockExtra, err := GetIbftExtra(currentBlock.ExtraData) + if err != nil { + return nil, err + } + + // calculate uptime for current epoch + for blockHeader.Number > epoch.FirstBlockInEpoch { + if err := getSealersForBlock(blockExtra, epoch.Validators); err != nil { + return nil, err + } + + blockHeader, blockExtra, err = getBlockData(blockHeader.Number-1, c.config.blockchain) + } + + // calculate uptime for blocks from previous epoch that were not processed in previous uptime + // since we can not calculate uptime for the last block in epoch (because of parent signatures) + if blockHeader.Number > uptimeLookbackSize { + for i := 0; i < uptimeLookbackSize; i++ { + validators, err := c.config.polybftBackend.GetValidators(blockHeader.Number-2, nil) + if err != nil { + return nil, err + } + + if err := getSealersForBlock(blockExtra, validators); err != nil { + return nil, err + } + + blockHeader, blockExtra, err = getBlockData(blockHeader.Number-1, c.config.blockchain) + } + } + + uptime := &contractsapi.Uptime{ + EpochID: new(big.Int).SetUint64(epochID), + TotalBlocks: big.NewInt(totalBlocks), + } + + // include the data in the uptime counter in a deterministic way + addrSet := []types.Address{} + + for addr := range uptimeCounter { + addrSet = append(addrSet, addr) + } + + sort.Slice(addrSet, func(i, j int) bool { + return bytes.Compare(addrSet[i][:], addrSet[j][:]) > 0 + }) + + for _, addr := range addrSet { + uptime.AddValidatorUptime(addr, uptimeCounter[addr]) + } + + commitEpoch := &contractsapi.CommitEpochFunction{ + ID: new(big.Int).SetUint64(epochID), + Epoch: &contractsapi.Epoch{ + StartBlock: new(big.Int).SetUint64(epoch.FirstBlockInEpoch), + EndBlock: new(big.Int).SetUint64(currentBlock.Number + 1), + EpochRoot: types.Hash{}, + }, + Uptime: uptime, + } + + return commitEpoch, nil +} + +// GenerateExitProof generates proof of exit and is a bridge endpoint store function +func (c *consensusRuntime) GenerateExitProof(exitID, epoch, checkpointBlock uint64) (types.Proof, error) { + return c.checkpointManager.GenerateExitProof(exitID, epoch, checkpointBlock) +} + +// GetStateSyncProof returns the proof for the state sync +func (c *consensusRuntime) GetStateSyncProof(stateSyncID uint64) (types.Proof, error) { + return c.stateSyncManager.GetStateSyncProof(stateSyncID) +} + +// setIsActiveValidator updates the activeValidatorFlag field +func (c *consensusRuntime) setIsActiveValidator(isActiveValidator bool) { + if isActiveValidator { + atomic.StoreUint32(&c.activeValidatorFlag, 1) + } else { + atomic.StoreUint32(&c.activeValidatorFlag, 0) + } +} + +// isActiveValidator indicates if node is in validator set or not +func (c *consensusRuntime) isActiveValidator() bool { + return atomic.LoadUint32(&c.activeValidatorFlag) == 1 +} + +// isFixedSizeOfEpochMet checks if epoch reached its end that was configured by its default size +// this is only true if no slashing occurred in the given epoch +func (c *consensusRuntime) isFixedSizeOfEpochMet(blockNumber uint64, epoch *epochMetadata) bool { + return epoch.FirstBlockInEpoch+c.config.PolyBFTConfig.EpochSize-1 == blockNumber +} + +// isFixedSizeOfSprintMet checks if an end of an sprint is reached with the current block +func (c *consensusRuntime) isFixedSizeOfSprintMet(blockNumber uint64, epoch *epochMetadata) bool { + return (blockNumber-epoch.FirstBlockInEpoch+1)%c.config.PolyBFTConfig.SprintSize == 0 +} + +// getSystemState builds SystemState instance for the most current block header +func (c *consensusRuntime) getSystemState(header *types.Header) (SystemState, error) { + provider, err := c.config.blockchain.GetStateProviderForBlock(header) + if err != nil { + return nil, err + } + + return c.config.blockchain.GetSystemState(c.config.PolyBFTConfig, provider), nil +} + +func (c *consensusRuntime) IsValidProposal(rawProposal []byte) bool { + if err := c.fsm.Validate(rawProposal); err != nil { + c.logger.Error("failed to validate proposal", "error", err) + + return false + } + + return true +} + +func (c *consensusRuntime) IsValidValidator(msg *proto.Message) bool { + c.lock.RLock() + defer c.lock.RUnlock() + + if c.fsm == nil { + c.logger.Warn("unable to validate IBFT message sender, because FSM is not initialized") + + return false + } + + if err := c.fsm.ValidateSender(msg); err != nil { + c.logger.Error("invalid IBFT message received", "error", err) + + return false + } + + return true +} + +func (c *consensusRuntime) IsProposer(id []byte, height, round uint64) bool { + c.lock.RLock() + defer c.lock.RUnlock() + + nextProposer, err := c.fsm.proposerSnapshot.CalcProposer(round, height) + if err != nil { + c.logger.Error("cannot calculate proposer", "error", err) + + return false + } + + c.logger.Info("Proposer calculated", "height", height, "round", round, "address", nextProposer) + + return bytes.Equal(id, nextProposer[:]) +} + +func (c *consensusRuntime) IsValidProposalHash(proposal *proto.Proposal, hash []byte) bool { + if len(proposal.RawProposal) == 0 { + c.logger.Error("proposal hash is not valid because proposal is empty") + + return false + } + + block := types.Block{} + if err := block.UnmarshalRLP(proposal.RawProposal); err != nil { + c.logger.Error("unable to unmarshal proposal", "error", err) + + return false + } + + extra, err := GetIbftExtra(block.Header.ExtraData) + if err != nil { + c.logger.Error("failed to retrieve extra", "block number", block.Number(), "error", err) + + return false + } + + proposalHash, err := extra.Checkpoint.Hash(c.config.blockchain.GetChainID(), block.Number(), block.Hash()) + if err != nil { + c.logger.Error("failed to calculate proposal hash", "block number", block.Number(), "error", err) + + return false + } + + return bytes.Equal(proposalHash.Bytes(), hash) +} + +func (c *consensusRuntime) IsValidCommittedSeal(proposalHash []byte, committedSeal *messages.CommittedSeal) bool { + err := c.fsm.ValidateCommit(committedSeal.Signer, committedSeal.Signature, proposalHash) + if err != nil { + c.logger.Info("Invalid committed seal", "error", err) + + return false + } + + return true +} + +func (c *consensusRuntime) BuildProposal(view *proto.View) []byte { + sharedData, err := c.getGuardedData() + if err != nil { + c.logger.Error("unable to build proposal", "error", err) + + return nil + } + + if sharedData.lastBuiltBlock.Number+1 != view.Height { + c.logger.Error("unable to build proposal, due to lack of parent block", + "parent height", sharedData.lastBuiltBlock.Number, "current height", view.Height) + + return nil + } + + proposal, err := c.fsm.BuildProposal(view.Round) + if err != nil { + c.logger.Error("unable to build proposal", "blockNumber", view, "error", err) + + return nil + } + + return proposal +} + +// InsertProposal inserts a proposal with the specified committed seals +func (c *consensusRuntime) InsertProposal(proposal *proto.Proposal, committedSeals []*messages.CommittedSeal) { + fsm := c.fsm + + fullBlock, err := fsm.Insert(proposal.RawProposal, committedSeals) + if err != nil { + c.logger.Error("cannot insert proposal", "error", err) + + return + } + + c.OnBlockInserted(fullBlock) +} + +// ID return ID (address actually) of the current node +func (c *consensusRuntime) ID() []byte { + return c.config.Key.Address().Bytes() +} + +// HasQuorum returns true if quorum is reached for the given blockNumber +func (c *consensusRuntime) HasQuorum( + height uint64, + messages []*proto.Message, + msgType proto.MessageType) bool { + c.lock.RLock() + defer c.lock.RUnlock() + // extract the addresses of all the signers of the messages + ppIncluded := false + signers := make(map[types.Address]struct{}, len(messages)) + + for _, message := range messages { + if message.Type == proto.MessageType_PREPREPARE { + ppIncluded = true + } + + signers[types.BytesToAddress(message.From)] = struct{}{} + } + + // check quorum + switch msgType { + case proto.MessageType_PREPREPARE: + return len(messages) >= 1 + case proto.MessageType_PREPARE: + if ppIncluded { + return c.fsm.validators.HasQuorum(signers) + } + + if len(messages) == 0 { + return false + } + + propAddress, err := c.fsm.proposerSnapshot.GetLatestProposer(messages[0].View.Round, height) + if err != nil { + c.logger.Warn("HasQuorum has been called but proposer is not set", "error", err) + + return false + } + + if _, ok := signers[propAddress]; ok { + c.logger.Warn("HasQuorum failed - proposer is among signers but it is not expected to be") + + return false + } + + signers[propAddress] = struct{}{} // add proposer manually + + return c.fsm.validators.HasQuorum(signers) + case proto.MessageType_ROUND_CHANGE, proto.MessageType_COMMIT: + return c.fsm.validators.HasQuorum(signers) + default: + return false + } +} + +// BuildPrePrepareMessage builds a PREPREPARE message based on the passed in proposal +func (c *consensusRuntime) BuildPrePrepareMessage( + rawProposal []byte, + certificate *proto.RoundChangeCertificate, + view *proto.View, +) *proto.Message { + if len(rawProposal) == 0 { + c.logger.Error("can not build pre-prepare message, since proposal is empty") + + return nil + } + + block := types.Block{} + if err := block.UnmarshalRLP(rawProposal); err != nil { + c.logger.Error(fmt.Sprintf("cannot unmarshal RLP: %s", err)) + + return nil + } + + extra, err := GetIbftExtra(block.Header.ExtraData) + if err != nil { + c.logger.Error("failed to retrieve extra for block %d: %w", block.Number(), err) + + return nil + } + + proposalHash, err := extra.Checkpoint.Hash(c.config.blockchain.GetChainID(), block.Number(), block.Hash()) + if err != nil { + c.logger.Error("failed to calculate proposal hash", "block number", block.Number(), "error", err) + + return nil + } + + proposal := &proto.Proposal{ + RawProposal: rawProposal, + Round: view.Round, + } + + msg := proto.Message{ + View: view, + From: c.ID(), + Type: proto.MessageType_PREPREPARE, + Payload: &proto.Message_PreprepareData{ + PreprepareData: &proto.PrePrepareMessage{ + Proposal: proposal, + ProposalHash: proposalHash.Bytes(), + Certificate: certificate, + }, + }, + } + + message, err := c.config.Key.SignEcdsaMessage(&msg) + if err != nil { + c.logger.Error("Cannot sign message", "error", err) + + return nil + } + + return message +} + +// BuildPrepareMessage builds a PREPARE message based on the passed in proposal +func (c *consensusRuntime) BuildPrepareMessage(proposalHash []byte, view *proto.View) *proto.Message { + msg := proto.Message{ + View: view, + From: c.ID(), + Type: proto.MessageType_PREPARE, + Payload: &proto.Message_PrepareData{ + PrepareData: &proto.PrepareMessage{ + ProposalHash: proposalHash, + }, + }, + } + + message, err := c.config.Key.SignEcdsaMessage(&msg) + if err != nil { + c.logger.Error("Cannot sign message.", "error", err) + + return nil + } + + return message +} + +// BuildCommitMessage builds a COMMIT message based on the passed in proposal +func (c *consensusRuntime) BuildCommitMessage(proposalHash []byte, view *proto.View) *proto.Message { + committedSeal, err := c.config.Key.Sign(proposalHash) + if err != nil { + c.logger.Error("Cannot create committed seal message.", "error", err) + + return nil + } + + msg := proto.Message{ + View: view, + From: c.ID(), + Type: proto.MessageType_COMMIT, + Payload: &proto.Message_CommitData{ + CommitData: &proto.CommitMessage{ + ProposalHash: proposalHash, + CommittedSeal: committedSeal, + }, + }, + } + + message, err := c.config.Key.SignEcdsaMessage(&msg) + if err != nil { + c.logger.Error("Cannot sign message", "Error", err) + + return nil + } + + return message +} + +// BuildRoundChangeMessage builds a ROUND_CHANGE message based on the passed in proposal +func (c *consensusRuntime) BuildRoundChangeMessage( + proposal *proto.Proposal, + certificate *proto.PreparedCertificate, + view *proto.View, +) *proto.Message { + msg := proto.Message{ + View: view, + From: c.ID(), + Type: proto.MessageType_ROUND_CHANGE, + Payload: &proto.Message_RoundChangeData{ + RoundChangeData: &proto.RoundChangeMessage{ + LastPreparedProposal: proposal, + LatestPreparedCertificate: certificate, + }}, + } + + signedMsg, err := c.config.Key.SignEcdsaMessage(&msg) + if err != nil { + c.logger.Error("Cannot sign message", "Error", err) + + return nil + } + + return signedMsg +} + +// getFirstBlockOfEpoch returns the first block of epoch in which provided header resides +func (c *consensusRuntime) getFirstBlockOfEpoch(epochNumber uint64, latestHeader *types.Header) (uint64, error) { + if latestHeader.Number == 0 { + // if we are starting the chain, we know that the first block is block 1 + return 1, nil + } + + blockHeader := latestHeader + + blockExtra, err := GetIbftExtra(latestHeader.ExtraData) + if err != nil { + return 0, err + } + + if epochNumber != blockExtra.Checkpoint.EpochNumber { + // its a regular epoch ending. No out of sync happened + return latestHeader.Number + 1, nil + } + + // node was out of sync, so we need to figure out what was the first block of the given epoch + epoch := blockExtra.Checkpoint.EpochNumber + + var firstBlockInEpoch uint64 + + for blockExtra.Checkpoint.EpochNumber == epoch { + firstBlockInEpoch = blockHeader.Number + blockHeader, blockExtra, err = getBlockData(blockHeader.Number-1, c.config.blockchain) + + if err != nil { + return 0, err + } + } + + return firstBlockInEpoch, nil +} + +// validateVote validates if the senders address is in active validator set +func validateVote(vote *MessageSignature, epoch *epochMetadata) error { + // get senders address + senderAddress := types.StringToAddress(vote.From) + if !epoch.Validators.ContainsAddress(senderAddress) { + return fmt.Errorf( + "message is received from sender %s, which is not in current validator set", + vote.From, + ) + } + + return nil +} + +// createExitTree creates an exit event merkle tree from provided exit events +func createExitTree(exitEvents []*ExitEvent) (*MerkleTree, error) { + numOfEvents := len(exitEvents) + data := make([][]byte, numOfEvents) + + for i := 0; i < numOfEvents; i++ { + b, err := ExitEventABIType.Encode(exitEvents[i]) + if err != nil { + return nil, err + } + + data[i] = b + } + + return NewMerkleTree(data) +} + +// getSealersForBlock checks who sealed a given block and updates the counter +func getSealersForBlock(sealersCounter map[types.Address]uint64, + blockExtra *Extra, validators AccountSet) error { + signers, err := validators.GetFilteredValidators(blockExtra.Parent.Bitmap) + if err != nil { + return err + } + + for _, a := range signers.GetAddresses() { + sealersCounter[a]++ + } + + return nil +} diff --git a/consensus/polybft/consensus_runtime_test.go b/consensus/polybft/consensus_runtime_test.go new file mode 100644 index 0000000000..525955e17c --- /dev/null +++ b/consensus/polybft/consensus_runtime_test.go @@ -0,0 +1,1176 @@ +package polybft + +import ( + "fmt" + "math/big" + "math/rand" + "os" + "testing" + "time" + + "github.com/0xPolygon/go-ibft/messages/proto" + "github.com/0xPolygon/polygon-edge/consensus" + "github.com/0xPolygon/polygon-edge/consensus/polybft/bitmap" + "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/crypto" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo" +) + +func TestConsensusRuntime_isFixedSizeOfEpochMet_NotReachedEnd(t *testing.T) { + t.Parallel() + + // because of slashing, we can assume some epochs started at random numbers + var cases = []struct { + epochSize, firstBlockInEpoch, parentBlockNumber uint64 + }{ + {4, 1, 2}, + {5, 1, 3}, + {6, 0, 6}, + {7, 0, 4}, + {8, 0, 5}, + {9, 4, 9}, + {10, 7, 10}, + {10, 1, 1}, + } + + runtime := &consensusRuntime{ + config: &runtimeConfig{ + PolyBFTConfig: &PolyBFTConfig{}, + }, + lastBuiltBlock: &types.Header{}, + epoch: &epochMetadata{}, + } + + for _, c := range cases { + runtime.config.PolyBFTConfig.EpochSize = c.epochSize + runtime.epoch.FirstBlockInEpoch = c.firstBlockInEpoch + assert.False( + t, + runtime.isFixedSizeOfEpochMet(c.parentBlockNumber+1, runtime.epoch), + fmt.Sprintf( + "Not expected end of epoch for epoch size=%v and parent block number=%v", + c.epochSize, + c.parentBlockNumber), + ) + } +} + +func TestConsensusRuntime_isFixedSizeOfEpochMet_ReachedEnd(t *testing.T) { + t.Parallel() + + // because of slashing, we can assume some epochs started at random numbers + var cases = []struct { + epochSize, firstBlockInEpoch, blockNumber uint64 + }{ + {4, 1, 4}, + {5, 1, 5}, + {6, 0, 5}, + {7, 0, 6}, + {8, 0, 7}, + {9, 4, 12}, + {10, 7, 16}, + {10, 1, 10}, + } + + runtime := &consensusRuntime{ + config: &runtimeConfig{ + PolyBFTConfig: &PolyBFTConfig{}, + }, + epoch: &epochMetadata{}, + } + + for _, c := range cases { + runtime.config.PolyBFTConfig.EpochSize = c.epochSize + runtime.epoch.FirstBlockInEpoch = c.firstBlockInEpoch + assert.True( + t, + runtime.isFixedSizeOfEpochMet(c.blockNumber, runtime.epoch), + fmt.Sprintf( + "Not expected end of epoch for epoch size=%v and parent block number=%v", + c.epochSize, + c.blockNumber), + ) + } +} + +func TestConsensusRuntime_isFixedSizeOfSprintMet_NotReachedEnd(t *testing.T) { + t.Parallel() + + // because of slashing, we can assume some epochs started at random numbers + var cases = []struct { + sprintSize, firstBlockInEpoch, blockNumber uint64 + }{ + {4, 1, 2}, + {5, 1, 3}, + {6, 0, 6}, + {7, 0, 4}, + {8, 0, 5}, + {9, 4, 9}, + {10, 7, 10}, + {10, 1, 1}, + } + + runtime := &consensusRuntime{ + config: &runtimeConfig{ + PolyBFTConfig: &PolyBFTConfig{}, + }, + epoch: &epochMetadata{}, + } + + for _, c := range cases { + runtime.config.PolyBFTConfig.SprintSize = c.sprintSize + runtime.epoch.FirstBlockInEpoch = c.firstBlockInEpoch + assert.False(t, + runtime.isFixedSizeOfSprintMet(c.blockNumber, runtime.epoch), + fmt.Sprintf( + "Not expected end of sprint for sprint size=%v and parent block number=%v", + c.sprintSize, + c.blockNumber), + ) + } +} + +func TestConsensusRuntime_isFixedSizeOfSprintMet_ReachedEnd(t *testing.T) { + t.Parallel() + + // because of slashing, we can assume some epochs started at random numbers + var cases = []struct { + sprintSize, firstBlockInEpoch, blockNumber uint64 + }{ + {4, 1, 4}, + {5, 1, 5}, + {6, 0, 5}, + {7, 0, 6}, + {8, 0, 7}, + {9, 4, 12}, + {10, 7, 16}, + {10, 1, 10}, + {5, 1, 10}, + {3, 3, 5}, + } + + runtime := &consensusRuntime{ + config: &runtimeConfig{ + PolyBFTConfig: &PolyBFTConfig{}, + }, + epoch: &epochMetadata{}, + } + + for _, c := range cases { + runtime.config.PolyBFTConfig.SprintSize = c.sprintSize + runtime.epoch.FirstBlockInEpoch = c.firstBlockInEpoch + assert.True(t, + runtime.isFixedSizeOfSprintMet(c.blockNumber, runtime.epoch), + fmt.Sprintf( + "Not expected end of sprint for sprint size=%v and parent block number=%v", + c.sprintSize, + c.blockNumber), + ) + } +} + +func TestConsensusRuntime_OnBlockInserted_EndOfEpoch(t *testing.T) { + t.Parallel() + + const ( + epochSize = uint64(10) + validatorsCount = 7 + ) + + currentEpochNumber := getEpochNumber(t, epochSize, epochSize) + validatorSet := newTestValidators(validatorsCount).getPublicIdentities() + header, headerMap := createTestBlocks(t, epochSize, epochSize, validatorSet) + builtBlock := consensus.BuildBlock(consensus.BuildBlockParams{ + Header: header, + }) + + newEpochNumber := currentEpochNumber + 1 + systemStateMock := new(systemStateMock) + systemStateMock.On("GetEpoch").Return(newEpochNumber).Once() + + blockchainMock := new(blockchainMock) + blockchainMock.On("GetStateProviderForBlock", mock.Anything).Return(new(stateProviderMock)).Once() + blockchainMock.On("GetSystemState", mock.Anything, mock.Anything).Return(systemStateMock) + blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headerMap.getHeader) + + polybftBackendMock := new(polybftBackendMock) + polybftBackendMock.On("GetValidators", mock.Anything, mock.Anything).Return(validatorSet).Times(3) + + txPool := new(txPoolMock) + txPool.On("ResetWithHeaders", mock.Anything).Once() + + snapshot := NewProposerSnapshot(epochSize-1, validatorSet) + config := &runtimeConfig{ + PolyBFTConfig: &PolyBFTConfig{ + EpochSize: epochSize, + }, + blockchain: blockchainMock, + polybftBackend: polybftBackendMock, + txPool: txPool, + State: newTestState(t), + } + runtime := &consensusRuntime{ + proposerCalculator: NewProposerCalculatorFromSnapshot(snapshot, config, hclog.NewNullLogger()), + logger: hclog.NewNullLogger(), + state: config.State, + config: config, + epoch: &epochMetadata{ + Number: currentEpochNumber, + FirstBlockInEpoch: header.Number - epochSize + 1, + }, + lastBuiltBlock: &types.Header{Number: header.Number - 1}, + stateSyncManager: &dummyStateSyncManager{}, + checkpointManager: &dummyCheckpointManager{}, + } + runtime.OnBlockInserted(&types.FullBlock{Block: builtBlock}) + + require.True(t, runtime.state.isEpochInserted(currentEpochNumber+1)) + require.Equal(t, newEpochNumber, runtime.epoch.Number) + + blockchainMock.AssertExpectations(t) + systemStateMock.AssertExpectations(t) +} + +func TestConsensusRuntime_OnBlockInserted_MiddleOfEpoch(t *testing.T) { + t.Parallel() + + const ( + epoch = 2 + epochSize = uint64(10) + firstBlockInEpoch = epochSize + 1 + blockNumber = epochSize + 2 + ) + + header := &types.Header{Number: blockNumber} + builtBlock := consensus.BuildBlock(consensus.BuildBlockParams{ + Header: header, + }) + + blockchainMock := new(blockchainMock) + blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(builtBlock.Header, true).Once() + + polybftBackendMock := new(polybftBackendMock) + polybftBackendMock.On("GetValidators", mock.Anything, mock.Anything).Return(nil).Once() + + txPool := new(txPoolMock) + txPool.On("ResetWithHeaders", mock.Anything).Once() + + snapshot := NewProposerSnapshot(blockNumber, []*ValidatorMetadata{}) + config := &runtimeConfig{ + PolyBFTConfig: &PolyBFTConfig{EpochSize: epochSize}, + blockchain: blockchainMock, + txPool: txPool, + } + + runtime := &consensusRuntime{ + lastBuiltBlock: header, + config: &runtimeConfig{ + PolyBFTConfig: &PolyBFTConfig{EpochSize: epochSize}, + blockchain: blockchainMock, + txPool: txPool, + }, + epoch: &epochMetadata{ + Number: epoch, + FirstBlockInEpoch: firstBlockInEpoch, + }, + logger: hclog.NewNullLogger(), + proposerCalculator: NewProposerCalculatorFromSnapshot(snapshot, config, hclog.NewNullLogger()), + } + runtime.OnBlockInserted(&types.FullBlock{Block: builtBlock}) + + require.Equal(t, header.Number, runtime.lastBuiltBlock.Number) +} + +func TestConsensusRuntime_FSM_NotInValidatorSet(t *testing.T) { + t.Parallel() + + validators := newTestValidatorsWithAliases([]string{"A", "B", "C", "D"}) + + snapshot := NewProposerSnapshot(1, nil) + config := &runtimeConfig{ + PolyBFTConfig: &PolyBFTConfig{ + EpochSize: 1, + }, + Key: createTestKey(t), + } + runtime := &consensusRuntime{ + proposerCalculator: NewProposerCalculatorFromSnapshot(snapshot, config, hclog.NewNullLogger()), + activeValidatorFlag: 1, + config: config, + epoch: &epochMetadata{ + Number: 1, + Validators: validators.getPublicIdentities(), + }, + lastBuiltBlock: &types.Header{}, + } + + err := runtime.FSM() + assert.ErrorIs(t, err, errNotAValidator) +} + +func TestConsensusRuntime_FSM_NotEndOfEpoch_NotEndOfSprint(t *testing.T) { + t.Parallel() + + extra := &Extra{ + Checkpoint: &CheckpointData{}, + } + lastBlock := &types.Header{ + Number: 1, + ExtraData: append(make([]byte, ExtraVanity), extra.MarshalRLPTo(nil)...), + } + + validators := newTestValidators(3) + blockchainMock := new(blockchainMock) + blockchainMock.On("NewBlockBuilder", mock.Anything).Return(&BlockBuilder{}, nil).Once() + + snapshot := NewProposerSnapshot(1, nil) + config := &runtimeConfig{ + PolyBFTConfig: &PolyBFTConfig{ + EpochSize: 10, + SprintSize: 5, + }, + Key: wallet.NewKey(validators.getPrivateIdentities()[0]), + blockchain: blockchainMock, + } + runtime := &consensusRuntime{ + proposerCalculator: NewProposerCalculatorFromSnapshot(snapshot, config, hclog.NewNullLogger()), + logger: hclog.NewNullLogger(), + activeValidatorFlag: 1, + config: config, + epoch: &epochMetadata{ + Number: 1, + Validators: validators.getPublicIdentities(), + FirstBlockInEpoch: 1, + }, + lastBuiltBlock: lastBlock, + state: newTestState(t), + stateSyncManager: &dummyStateSyncManager{}, + checkpointManager: &dummyCheckpointManager{}, + } + + err := runtime.FSM() + require.NoError(t, err) + + assert.True(t, runtime.isActiveValidator()) + assert.False(t, runtime.fsm.isEndOfEpoch) + assert.False(t, runtime.fsm.isEndOfSprint) + assert.Equal(t, lastBlock.Number, runtime.fsm.parent.Number) + + address := types.Address(runtime.config.Key.Address()) + assert.True(t, runtime.fsm.ValidatorSet().Includes(address)) + + assert.NotNil(t, runtime.fsm.blockBuilder) + assert.NotNil(t, runtime.fsm.backend) + + blockchainMock.AssertExpectations(t) +} + +func TestConsensusRuntime_FSM_EndOfEpoch_BuildUptime(t *testing.T) { + t.Parallel() + + const ( + epoch = 0 + epochSize = uint64(10) + sprintSize = uint64(3) + firstBlockInEpoch = uint64(1) + fromIndex = uint64(0) + toIndex = uint64(9) + ) + + validatorAccounts := newTestValidatorsWithAliases([]string{"A", "B", "C", "D", "E", "F"}) + validators := validatorAccounts.getPublicIdentities() + + lastBuiltBlock, headerMap := createTestBlocks(t, 9, epochSize, validators) + + blockchainMock := new(blockchainMock) + blockchainMock.On("NewBlockBuilder", mock.Anything).Return(&BlockBuilder{}, nil).Once() + blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headerMap.getHeader) + + state := newTestState(t) + require.NoError(t, state.insertEpoch(epoch)) + + metadata := &epochMetadata{ + Validators: validators, + Number: epoch, + FirstBlockInEpoch: firstBlockInEpoch, + } + + config := &runtimeConfig{ + PolyBFTConfig: &PolyBFTConfig{ + EpochSize: epochSize, + SprintSize: sprintSize, + }, + Key: validatorAccounts.getValidator("A").Key(), + blockchain: blockchainMock, + } + + snapshot := NewProposerSnapshot(1, nil) + runtime := &consensusRuntime{ + proposerCalculator: NewProposerCalculatorFromSnapshot(snapshot, config, hclog.NewNullLogger()), + logger: hclog.NewNullLogger(), + state: state, + epoch: metadata, + config: config, + lastBuiltBlock: lastBuiltBlock, + stateSyncManager: &dummyStateSyncManager{}, + checkpointManager: &dummyCheckpointManager{}, + } + + err := runtime.FSM() + fsm := runtime.fsm + + assert.NoError(t, err) + assert.True(t, fsm.isEndOfEpoch) + assert.NotNil(t, fsm.uptimeCounter) + assert.NotEmpty(t, fsm.uptimeCounter) + + blockchainMock.AssertExpectations(t) +} + +func Test_NewConsensusRuntime(t *testing.T) { + t.Parallel() + + _, err := os.Create("/tmp/consensusState.db") + require.NoError(t, err) + + polyBftConfig := &PolyBFTConfig{ + Bridge: &BridgeConfig{ + BridgeAddr: types.Address{0x13}, + CheckpointAddr: types.Address{0x10}, + JSONRPCEndpoint: "testEndpoint", + }, + ValidatorSetAddr: types.Address{0x11}, + EpochSize: 10, + SprintSize: 10, + BlockTime: 2 * time.Second, + } + + validators := newTestValidators(3).getPublicIdentities() + + systemStateMock := new(systemStateMock) + systemStateMock.On("GetEpoch").Return(uint64(1)).Once() + systemStateMock.On("GetNextCommittedIndex").Return(uint64(1)).Once() + + blockchainMock := &blockchainMock{} + blockchainMock.On("CurrentHeader").Return(&types.Header{Number: 1, ExtraData: createTestExtraForAccounts(t, 1, validators, nil)}) + blockchainMock.On("GetStateProviderForBlock", mock.Anything).Return(new(stateProviderMock)).Once() + blockchainMock.On("GetSystemState", mock.Anything, mock.Anything).Return(systemStateMock).Once() + blockchainMock.On("GetHeaderByNumber", uint64(0)).Return(&types.Header{Number: 0, ExtraData: createTestExtraForAccounts(t, 0, validators, nil)}) + + polybftBackendMock := new(polybftBackendMock) + polybftBackendMock.On("GetValidators", mock.Anything, mock.Anything).Return(validators).Twice() + + tmpDir := t.TempDir() + config := &runtimeConfig{ + polybftBackend: polybftBackendMock, + State: newTestState(t), + PolyBFTConfig: polyBftConfig, + DataDir: tmpDir, + Key: createTestKey(t), + blockchain: blockchainMock, + bridgeTopic: &mockTopic{}, + } + runtime, err := newConsensusRuntime(hclog.NewNullLogger(), config) + require.NoError(t, err) + + assert.False(t, runtime.isActiveValidator()) + assert.Equal(t, runtime.config.DataDir, tmpDir) + assert.Equal(t, uint64(10), runtime.config.PolyBFTConfig.SprintSize) + assert.Equal(t, uint64(10), runtime.config.PolyBFTConfig.EpochSize) + assert.Equal(t, "0x1100000000000000000000000000000000000000", runtime.config.PolyBFTConfig.ValidatorSetAddr.String()) + assert.Equal(t, "0x1300000000000000000000000000000000000000", runtime.config.PolyBFTConfig.Bridge.BridgeAddr.String()) + assert.Equal(t, "0x1000000000000000000000000000000000000000", runtime.config.PolyBFTConfig.Bridge.CheckpointAddr.String()) + assert.True(t, runtime.IsBridgeEnabled()) + systemStateMock.AssertExpectations(t) + blockchainMock.AssertExpectations(t) + polybftBackendMock.AssertExpectations(t) +} + +func TestConsensusRuntime_restartEpoch_SameEpochNumberAsTheLastOne(t *testing.T) { + t.Parallel() + + const originalBlockNumber = uint64(5) + + newCurrentHeader := &types.Header{Number: originalBlockNumber + 1} + validatorSet := newTestValidators(3).getPublicIdentities() + + systemStateMock := new(systemStateMock) + systemStateMock.On("GetEpoch").Return(uint64(1), nil).Once() + + blockchainMock := new(blockchainMock) + blockchainMock.On("GetStateProviderForBlock", mock.Anything).Return(new(stateProviderMock)).Once() + blockchainMock.On("GetSystemState", mock.Anything, mock.Anything).Return(systemStateMock).Once() + + snapshot := NewProposerSnapshot(1, nil) + config := &runtimeConfig{ + blockchain: blockchainMock, + } + runtime := &consensusRuntime{ + proposerCalculator: NewProposerCalculatorFromSnapshot(snapshot, config, hclog.NewNullLogger()), + activeValidatorFlag: 1, + config: config, + epoch: &epochMetadata{ + Number: 1, + Validators: validatorSet, + FirstBlockInEpoch: 1, + }, + lastBuiltBlock: &types.Header{ + Number: originalBlockNumber, + }, + } + + epoch, err := runtime.restartEpoch(newCurrentHeader) + + require.NoError(t, err) + + for _, a := range validatorSet.GetAddresses() { + assert.True(t, epoch.Validators.ContainsAddress(a)) + } + + systemStateMock.AssertExpectations(t) + blockchainMock.AssertExpectations(t) +} + +func TestConsensusRuntime_calculateUptime_SecondEpoch(t *testing.T) { + t.Parallel() + + const ( + epoch = 2 + epochSize = 10 + epochStartBlock = 11 + epochEndBlock = 20 + sprintSize = 5 + ) + + validators := newTestValidatorsWithAliases([]string{"A", "B", "C", "D", "E"}) + polybftConfig := &PolyBFTConfig{ + ValidatorSetAddr: contracts.ValidatorSetContract, + EpochSize: epochSize, + SprintSize: sprintSize, + } + + lastBuiltBlock, headerMap := createTestBlocks(t, 19, epochSize, validators.getPublicIdentities()) + + blockchainMock := new(blockchainMock) + blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headerMap.getHeader) + + polybftBackendMock := new(polybftBackendMock) + polybftBackendMock.On("GetValidators", mock.Anything, mock.Anything).Return(validators.getPublicIdentities()).Twice() + + config := &runtimeConfig{ + PolyBFTConfig: polybftConfig, + blockchain: blockchainMock, + polybftBackend: polybftBackendMock, + Key: validators.getValidator("A").Key(), + } + + consensusRuntime := &consensusRuntime{ + config: config, + epoch: &epochMetadata{ + Number: epoch, + Validators: validators.getPublicIdentities(), + FirstBlockInEpoch: epochStartBlock, + }, + lastBuiltBlock: lastBuiltBlock, + } + + uptime, err := consensusRuntime.calculateUptime(lastBuiltBlock, consensusRuntime.epoch) + assert.NoError(t, err) + assert.NotEmpty(t, uptime) + assert.Equal(t, uint64(epoch), uptime.ID.Uint64()) + assert.Equal(t, uint64(epochStartBlock), uptime.Epoch.StartBlock.Uint64()) + assert.Equal(t, uint64(epochEndBlock), uptime.Epoch.EndBlock.Uint64()) + + blockchainMock.AssertExpectations(t) + polybftBackendMock.AssertExpectations(t) +} + +func TestConsensusRuntime_validateVote_VoteSentFromUnknownValidator(t *testing.T) { + t.Parallel() + + epoch := &epochMetadata{Validators: newTestValidators(5).getPublicIdentities()} + nonValidatorAccount := createTestKey(t) + hash := crypto.Keccak256Hash(generateRandomBytes(t)).Bytes() + // Sign content by non validator account + signature, err := nonValidatorAccount.Sign(hash) + require.NoError(t, err) + + vote := &MessageSignature{ + From: nonValidatorAccount.String(), + Signature: signature} + assert.ErrorContains(t, validateVote(vote, epoch), + fmt.Sprintf("message is received from sender %s, which is not in current validator set", vote.From)) +} + +func TestConsensusRuntime_IsValidSender(t *testing.T) { + t.Parallel() + + validatorAccounts := newTestValidatorsWithAliases([]string{"A", "B", "C", "D", "E", "F"}) + + extra := &Extra{} + lastBuildBlock := &types.Header{ + Number: 0, + ExtraData: append(make([]byte, ExtraVanity), extra.MarshalRLPTo(nil)...), + } + + blockchainMock := new(blockchainMock) + blockchainMock.On("NewBlockBuilder", mock.Anything).Return(&BlockBuilder{}, nil).Once() + + state := newTestState(t) + snapshot := NewProposerSnapshot(0, nil) + config := &runtimeConfig{ + Key: validatorAccounts.getValidator("B").Key(), + blockchain: blockchainMock, + PolyBFTConfig: &PolyBFTConfig{EpochSize: 10, SprintSize: 5}, + } + runtime := &consensusRuntime{ + state: state, + config: config, + lastBuiltBlock: lastBuildBlock, + epoch: &epochMetadata{ + Number: 1, + Validators: validatorAccounts.getPublicIdentities()[:len(validatorAccounts.validators)-1], + }, + logger: hclog.NewNullLogger(), + proposerCalculator: NewProposerCalculatorFromSnapshot(snapshot, config, hclog.NewNullLogger()), + stateSyncManager: &dummyStateSyncManager{}, + checkpointManager: &dummyCheckpointManager{}, + } + + require.NoError(t, runtime.FSM()) + + sender := validatorAccounts.getValidator("A") + msg, err := sender.Key().SignEcdsaMessage(&proto.Message{ + From: sender.Address().Bytes(), + }) + + require.NoError(t, err) + + assert.True(t, runtime.IsValidValidator(msg)) + blockchainMock.AssertExpectations(t) + + // sender not in current epoch validators + sender = validatorAccounts.getValidator("F") + msg, err = sender.Key().SignEcdsaMessage(&proto.Message{ + From: sender.Address().Bytes(), + }) + + require.NoError(t, err) + + assert.False(t, runtime.IsValidValidator(msg)) + blockchainMock.AssertExpectations(t) + + // signature does not come from sender + sender = validatorAccounts.getValidator("A") + msg, err = sender.Key().SignEcdsaMessage(&proto.Message{ + From: validatorAccounts.getValidator("B").Address().Bytes(), + }) + + require.NoError(t, err) + + assert.False(t, runtime.IsValidValidator(msg)) + blockchainMock.AssertExpectations(t) + + // invalid signature + sender = validatorAccounts.getValidator("A") + msg = &proto.Message{ + From: sender.Address().Bytes(), + Signature: []byte{1, 2}, + } + + assert.False(t, runtime.IsValidValidator(msg)) + blockchainMock.AssertExpectations(t) +} + +func TestConsensusRuntime_IsValidProposalHash(t *testing.T) { + t.Parallel() + + extra := &Extra{ + Checkpoint: &CheckpointData{ + EpochNumber: 1, + BlockRound: 1, + }, + } + block := &types.Block{ + Header: &types.Header{ + Number: 10, + ExtraData: append(make([]byte, ExtraVanity), extra.MarshalRLPTo(nil)...), + }, + } + block.Header.ComputeHash() + + proposalHash, err := extra.Checkpoint.Hash(0, block.Number(), block.Hash()) + require.NoError(t, err) + + runtime := &consensusRuntime{ + logger: hclog.NewNullLogger(), + config: &runtimeConfig{blockchain: new(blockchainMock)}, + } + + require.True(t, runtime.IsValidProposalHash(&proto.Proposal{RawProposal: block.MarshalRLP()}, proposalHash.Bytes())) +} + +func TestConsensusRuntime_IsValidProposalHash_InvalidProposalHash(t *testing.T) { + t.Parallel() + + extra := &Extra{ + Checkpoint: &CheckpointData{ + EpochNumber: 1, + BlockRound: 1, + }, + } + + block := &types.Block{ + Header: &types.Header{ + Number: 10, + ExtraData: append(make([]byte, ExtraVanity), extra.MarshalRLPTo(nil)...), + }, + } + + proposalHash, err := extra.Checkpoint.Hash(0, block.Number(), block.Hash()) + require.NoError(t, err) + + extra.Checkpoint.BlockRound = 2 // change it so it is not the same as in proposal hash + block.Header.ExtraData = append(make([]byte, ExtraVanity), extra.MarshalRLPTo(nil)...) + block.Header.ComputeHash() + + runtime := &consensusRuntime{ + logger: hclog.NewNullLogger(), + config: &runtimeConfig{blockchain: new(blockchainMock)}, + } + + require.False(t, runtime.IsValidProposalHash(&proto.Proposal{RawProposal: block.MarshalRLP()}, proposalHash.Bytes())) +} + +func TestConsensusRuntime_IsValidProposalHash_InvalidExtra(t *testing.T) { + t.Parallel() + + extra := &Extra{ + Checkpoint: &CheckpointData{ + EpochNumber: 1, + BlockRound: 1, + }, + } + + block := &types.Block{ + Header: &types.Header{ + Number: 10, + ExtraData: []byte{1, 2, 3}, // invalid extra in block + }, + } + block.Header.ComputeHash() + + proposalHash, err := extra.Checkpoint.Hash(0, block.Number(), block.Hash()) + require.NoError(t, err) + + runtime := &consensusRuntime{ + logger: hclog.NewNullLogger(), + config: &runtimeConfig{blockchain: new(blockchainMock)}, + } + + require.False(t, runtime.IsValidProposalHash(&proto.Proposal{RawProposal: block.MarshalRLP()}, proposalHash.Bytes())) +} + +func TestConsensusRuntime_BuildProposal_InvalidParent(t *testing.T) { + config := &runtimeConfig{} + snapshot := NewProposerSnapshot(1, nil) + runtime := &consensusRuntime{ + logger: hclog.NewNullLogger(), + lastBuiltBlock: &types.Header{Number: 2}, + epoch: &epochMetadata{Number: 1}, + config: config, + proposerCalculator: NewProposerCalculatorFromSnapshot(snapshot, config, hclog.NewNullLogger()), + } + + require.Nil(t, runtime.BuildProposal(&proto.View{Round: 5})) +} + +func TestConsensusRuntime_ID(t *testing.T) { + t.Parallel() + + key1, key2 := createTestKey(t), createTestKey(t) + runtime := &consensusRuntime{ + config: &runtimeConfig{Key: key1}, + } + + require.Equal(t, runtime.ID(), key1.Address().Bytes()) + require.NotEqual(t, runtime.ID(), key2.Address().Bytes()) +} + +func TestConsensusRuntime_HasQuorum(t *testing.T) { + t.Parallel() + + const round = 5 + + validatorAccounts := newTestValidatorsWithAliases([]string{"A", "B", "C", "D", "E", "F"}) + + extra := &Extra{ + Checkpoint: &CheckpointData{}, + } + + lastBuildBlock := &types.Header{ + Number: 1, + ExtraData: append(make([]byte, ExtraVanity), extra.MarshalRLPTo(nil)...), + } + + blockchainMock := new(blockchainMock) + blockchainMock.On("NewBlockBuilder", mock.Anything).Return(&BlockBuilder{}, nil).Once() + blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(&types.Header{ + Number: 0, + }, true).Once() + + state := newTestState(t) + snapshot := NewProposerSnapshot(lastBuildBlock.Number+1, validatorAccounts.getPublicIdentities()) + config := &runtimeConfig{ + Key: validatorAccounts.getValidator("B").Key(), + blockchain: blockchainMock, + PolyBFTConfig: &PolyBFTConfig{EpochSize: 10, SprintSize: 5}, + } + runtime := &consensusRuntime{ + state: state, + config: config, + lastBuiltBlock: lastBuildBlock, + epoch: &epochMetadata{ + Number: 1, + Validators: validatorAccounts.getPublicIdentities()[:len(validatorAccounts.validators)-1], + }, + logger: hclog.NewNullLogger(), + proposerCalculator: NewProposerCalculatorFromSnapshot(snapshot, config, hclog.NewNullLogger()), + stateSyncManager: &dummyStateSyncManager{}, + checkpointManager: &dummyCheckpointManager{}, + } + + require.NoError(t, runtime.FSM()) + + proposer, err := snapshot.CalcProposer(round, lastBuildBlock.Number+1) + require.NoError(t, err) + + runtime.fsm.proposerSnapshot = snapshot + + messages := make([]*proto.Message, 0, len(validatorAccounts.validators)) + + // Unknown message type + assert.False(t, runtime.HasQuorum(lastBuildBlock.Number+1, messages, -1)) + + // invalid block number + for _, msgType := range []proto.MessageType{proto.MessageType_PREPREPARE, proto.MessageType_PREPARE, + proto.MessageType_ROUND_CHANGE, proto.MessageType_COMMIT} { + assert.False(t, runtime.HasQuorum(lastBuildBlock.Number, nil, msgType)) + } + + // MessageType_PREPREPARE - only one message is enough + messages = append(messages, &proto.Message{ + From: proposer.Bytes(), + Type: proto.MessageType_PREPREPARE, + View: &proto.View{Height: 1, Round: round}, + }) + + assert.False(t, runtime.HasQuorum(lastBuildBlock.Number+1, nil, proto.MessageType_PREPREPARE)) + assert.True(t, runtime.HasQuorum(lastBuildBlock.Number+1, messages, proto.MessageType_PREPREPARE)) + + // MessageType_PREPARE + messages = make([]*proto.Message, 0, len(validatorAccounts.validators)) + + for _, x := range validatorAccounts.validators { + address := x.Address() + + // proposer must not be included in prepare messages + if address != proposer { + messages = append(messages, &proto.Message{ + From: address[:], + Type: proto.MessageType_PREPARE, + View: &proto.View{Height: lastBuildBlock.Number + 1, Round: round}, + }) + } + } + + // enough quorum + assert.True(t, runtime.HasQuorum(lastBuildBlock.Number+1, messages, proto.MessageType_PREPARE)) + + // not enough quorum + assert.False(t, runtime.HasQuorum(lastBuildBlock.Number+1, messages[:1], proto.MessageType_PREPARE)) + + // include proposer which is not allowed + messages = append(messages, &proto.Message{ + From: proposer[:], + Type: proto.MessageType_PREPARE, + View: &proto.View{Height: lastBuildBlock.Number + 1, Round: round}, + }) + assert.False(t, runtime.HasQuorum(lastBuildBlock.Number+1, messages, proto.MessageType_PREPARE)) + + // last message is MessageType_PREPREPARE - this should be allowed + messages[len(messages)-1].Type = proto.MessageType_PREPREPARE + assert.True(t, runtime.HasQuorum(lastBuildBlock.Number+1, messages, proto.MessageType_PREPARE)) + + //proto.MessageType_ROUND_CHANGE, proto.MessageType_COMMIT + for _, msgType := range []proto.MessageType{proto.MessageType_ROUND_CHANGE, proto.MessageType_COMMIT} { + messages = make([]*proto.Message, 0, len(validatorAccounts.validators)) + + for _, x := range validatorAccounts.validators { + messages = append(messages, &proto.Message{ + From: x.Address().Bytes(), + Type: msgType, + View: &proto.View{Height: lastBuildBlock.Number + 1, Round: round}, + }) + } + + assert.True(t, runtime.HasQuorum(lastBuildBlock.Number+1, messages, msgType)) + assert.False(t, runtime.HasQuorum(lastBuildBlock.Number+1, messages[:1], msgType)) + } +} + +func TestConsensusRuntime_BuildRoundChangeMessage(t *testing.T) { + t.Parallel() + + key := createTestKey(t) + view, rawProposal, certificate := &proto.View{}, []byte{1}, &proto.PreparedCertificate{} + + runtime := &consensusRuntime{ + config: &runtimeConfig{ + Key: key, + }, + } + + proposal := &proto.Proposal{ + RawProposal: rawProposal, + Round: view.Round, + } + + expected := proto.Message{ + View: view, + From: key.Address().Bytes(), + Type: proto.MessageType_ROUND_CHANGE, + Payload: &proto.Message_RoundChangeData{RoundChangeData: &proto.RoundChangeMessage{ + LatestPreparedCertificate: certificate, + LastPreparedProposal: proposal, + }}, + } + + signedMsg, err := key.SignEcdsaMessage(&expected) + require.NoError(t, err) + + assert.Equal(t, signedMsg, runtime.BuildRoundChangeMessage(proposal, certificate, view)) +} + +func TestConsensusRuntime_BuildCommitMessage(t *testing.T) { + t.Parallel() + + key := createTestKey(t) + view, proposalHash := &proto.View{}, []byte{1, 2, 4} + + runtime := &consensusRuntime{ + config: &runtimeConfig{ + Key: key, + }, + } + + committedSeal, err := key.Sign(proposalHash) + require.NoError(t, err) + + expected := proto.Message{ + View: view, + From: key.Address().Bytes(), + Type: proto.MessageType_COMMIT, + Payload: &proto.Message_CommitData{ + CommitData: &proto.CommitMessage{ + ProposalHash: proposalHash, + CommittedSeal: committedSeal, + }, + }, + } + + signedMsg, err := key.SignEcdsaMessage(&expected) + require.NoError(t, err) + + assert.Equal(t, signedMsg, runtime.BuildCommitMessage(proposalHash, view)) +} + +func TestConsensusRuntime_BuildPrePrepareMessage_EmptyProposal(t *testing.T) { + t.Parallel() + + runtime := &consensusRuntime{logger: hclog.NewNullLogger()} + + assert.Nil(t, runtime.BuildPrePrepareMessage(nil, &proto.RoundChangeCertificate{}, &proto.View{Height: 1, Round: 0})) +} + +func TestConsensusRuntime_IsValidProposalHash_EmptyProposal(t *testing.T) { + t.Parallel() + + runtime := &consensusRuntime{logger: hclog.NewNullLogger()} + + assert.False(t, runtime.IsValidProposalHash(&proto.Proposal{}, []byte("hash"))) +} + +func TestConsensusRuntime_BuildPrepareMessage(t *testing.T) { + t.Parallel() + + key := createTestKey(t) + view, proposalHash := &proto.View{}, []byte{1, 2, 4} + + runtime := &consensusRuntime{ + config: &runtimeConfig{ + Key: key, + }, + } + + expected := proto.Message{ + View: view, + From: key.Address().Bytes(), + Type: proto.MessageType_PREPARE, + Payload: &proto.Message_PrepareData{ + PrepareData: &proto.PrepareMessage{ + ProposalHash: proposalHash, + }, + }, + } + + signedMsg, err := key.SignEcdsaMessage(&expected) + require.NoError(t, err) + + assert.Equal(t, signedMsg, runtime.BuildPrepareMessage(proposalHash, view)) +} + +func createTestTransportMessage(hash []byte, epochNumber uint64, key *wallet.Key) *TransportMessage { + signature, _ := key.Sign(hash) + + return &TransportMessage{ + Hash: hash, + Signature: signature, + NodeID: key.String(), + EpochNumber: epochNumber, + } +} + +func createTestMessageVote(t *testing.T, hash []byte, validator *testValidator) *MessageSignature { + t.Helper() + + signature, err := validator.mustSign(hash).Marshal() + require.NoError(t, err) + + return &MessageSignature{ + From: validator.Key().String(), + Signature: signature, + } +} + +func createTestBlocks(t *testing.T, numberOfBlocks, defaultEpochSize uint64, + validatorSet AccountSet) (*types.Header, *testHeadersMap) { + t.Helper() + + headerMap := &testHeadersMap{} + bitmaps := createTestBitmaps(t, validatorSet, numberOfBlocks) + + extra := &Extra{ + Checkpoint: &CheckpointData{EpochNumber: 0}, + } + + genesisBlock := &types.Header{ + Number: 0, + ExtraData: append(make([]byte, ExtraVanity), extra.MarshalRLPTo(nil)...), + } + parentHash := types.BytesToHash(big.NewInt(0).Bytes()) + + headerMap.addHeader(genesisBlock) + + var hash types.Hash + + var blockHeader *types.Header + + for i := uint64(1); i <= numberOfBlocks; i++ { + big := big.NewInt(int64(i)) + hash = types.BytesToHash(big.Bytes()) + + header := &types.Header{ + Number: i, + ParentHash: parentHash, + ExtraData: createTestExtraForAccounts(t, getEpochNumber(t, i, defaultEpochSize), validatorSet, bitmaps[i]), + GasLimit: types.StateTransactionGasLimit, + } + + headerMap.addHeader(header) + + parentHash = hash + blockHeader = header + } + + return blockHeader, headerMap +} + +func createTestBitmaps(t *testing.T, validators AccountSet, numberOfBlocks uint64) map[uint64]bitmap.Bitmap { + t.Helper() + + bitmaps := make(map[uint64]bitmap.Bitmap, numberOfBlocks) + + rand.Seed(time.Now().Unix()) + + for i := numberOfBlocks; i > 1; i-- { + bitmap := bitmap.Bitmap{} + j := 0 + + for j != 3 { + validator := validators[rand.Intn(validators.Len())] + index := uint64(validators.Index(validator.Address)) + + if !bitmap.IsSet(index) { + bitmap.Set(index) + j++ + } + } + + bitmaps[i] = bitmap + } + + return bitmaps +} + +func createTestExtraForAccounts(t *testing.T, epoch uint64, validators AccountSet, b bitmap.Bitmap) []byte { + t.Helper() + + dummySignature := [64]byte{} + extraData := Extra{ + Validators: &ValidatorSetDelta{ + Added: validators, + Removed: bitmap.Bitmap{}, + }, + Parent: &Signature{Bitmap: b, AggregatedSignature: dummySignature[:]}, + Committed: &Signature{Bitmap: b, AggregatedSignature: dummySignature[:]}, + Checkpoint: &CheckpointData{EpochNumber: epoch}, + } + + marshaled := extraData.MarshalRLPTo(nil) + result := make([]byte, ExtraVanity+len(marshaled)) + + copy(result[ExtraVanity:], marshaled) + + return result +} + +func setupExitEventsForProofVerification(t *testing.T, state *State, + numOfBlocks, numOfEventsPerBlock uint64) [][]byte { + t.Helper() + + encodedEvents := make([][]byte, numOfBlocks*numOfEventsPerBlock) + index := uint64(0) + + for i := uint64(1); i <= numOfBlocks; i++ { + for j := uint64(1); j <= numOfEventsPerBlock; j++ { + e := &ExitEvent{index, ethgo.ZeroAddress, ethgo.ZeroAddress, []byte{0, 1}, 1, i} + require.NoError(t, state.insertExitEvent(e)) + + b, err := ExitEventABIType.Encode(e) + + require.NoError(t, err) + + encodedEvents[index] = b + index++ + } + } + + return encodedEvents +} diff --git a/consensus/polybft/contracts_initializer.go b/consensus/polybft/contracts_initializer.go new file mode 100644 index 0000000000..d96e729c8f --- /dev/null +++ b/consensus/polybft/contracts_initializer.go @@ -0,0 +1,94 @@ +package polybft + +import ( + "encoding/hex" + "fmt" + "math/big" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/state" + "github.com/0xPolygon/polygon-edge/types" + "github.com/umbracle/ethgo/abi" +) + +const ( + // safe numbers for the test + minStake = 1 + minDelegation = 1 +) + +var ( + nativeTokenName = "Polygon" + nativeTokenSymbol = "MATIC" +) + +func getInitChildValidatorSetInput(polyBFTConfig PolyBFTConfig) ([]byte, error) { + validatorAddresses := make([]types.Address, len(polyBFTConfig.InitialValidatorSet)) + validatorPubkeys := make([][4]*big.Int, len(polyBFTConfig.InitialValidatorSet)) + validatorStakes := make([]*big.Int, len(polyBFTConfig.InitialValidatorSet)) + + for i, validator := range polyBFTConfig.InitialValidatorSet { + blsKey, err := hex.DecodeString(validator.BlsKey) + if err != nil { + return nil, err + } + + pubKey, err := bls.UnmarshalPublicKey(blsKey) + if err != nil { + return nil, err + } + + pubKeyBig := pubKey.ToBigInt() + + validatorPubkeys[i] = pubKeyBig + validatorAddresses[i] = validator.Address + validatorStakes[i] = new(big.Int).Set(validator.Balance) + } + + registerMessage, err := bls.MarshalMessageToBigInt([]byte(contracts.PolyBFTRegisterMessage)) + if err != nil { + return nil, err + } + + params := map[string]interface{}{ + "init": map[string]interface{}{ + "epochReward": new(big.Int).SetUint64(polyBFTConfig.EpochReward), + "minStake": big.NewInt(minStake), + "minDelegation": big.NewInt(minDelegation), + "epochSize": new(big.Int).SetUint64(polyBFTConfig.EpochSize), + }, + "validatorAddresses": validatorAddresses, + "validatorPubkeys": validatorPubkeys, + "validatorStakes": validatorStakes, + "newBls": contracts.BLSContract, // address of the deployed BLS contract + "newMessage": registerMessage, + "governance": polyBFTConfig.Governance, + } + + input, err := contractsapi.ChildValidatorSet.Abi.Methods["initialize"].Encode(params) + if err != nil { + return nil, err + } + + return input, nil +} + +func initContract(to types.Address, input []byte, contractName string, transition *state.Transition) error { + result := transition.Call2(contracts.SystemCaller, to, input, + big.NewInt(0), 100_000_000) + + if result.Failed() { + if result.Reverted() { + unpackedRevert, err := abi.UnpackRevertError(result.ReturnValue) + if err == nil { + fmt.Printf("%v.initialize %v\n", contractName, unpackedRevert) + } + } + + return result.Err + } + + return nil +} diff --git a/consensus/polybft/contractsapi/artifact/artifacts.go b/consensus/polybft/contractsapi/artifact/artifacts.go new file mode 100644 index 0000000000..23bacf2c1f --- /dev/null +++ b/consensus/polybft/contractsapi/artifact/artifacts.go @@ -0,0 +1,56 @@ +package artifact + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "path/filepath" + + "github.com/0xPolygon/polygon-edge/helper/hex" + "github.com/umbracle/ethgo/abi" +) + +func ReadArtifact(rootFolder, contractPath, contractName string) (*Artifact, error) { + data, err := ReadArtifactData(rootFolder, contractPath, contractName) + if err != nil { + return nil, err + } + + return DecodeArtifact(data) +} + +func ReadArtifactData(rootFolder, contractPath, contractName string) ([]byte, error) { + fileName := filepath.Join(rootFolder, contractPath, fmt.Sprintf("%s.json", contractName)) + + absolutePath, err := filepath.Abs(fileName) + if err != nil { + return nil, err + } + + return ioutil.ReadFile(filepath.Clean(absolutePath)) +} + +func DecodeArtifact(data []byte) (*Artifact, error) { + var hexRes HexArtifact + if err := json.Unmarshal(data, &hexRes); err != nil { + return nil, fmt.Errorf("artifact found but no correct format: %w", err) + } + + return &Artifact{ + Abi: hexRes.Abi, + Bytecode: hex.MustDecodeHex(hexRes.ByteCode), + DeployedBytecode: hex.MustDecodeHex(hexRes.DeployedBytecode), + }, nil +} + +type HexArtifact struct { + Abi *abi.ABI + ByteCode string + DeployedBytecode string +} + +type Artifact struct { + Abi *abi.ABI + Bytecode []byte + DeployedBytecode []byte +} diff --git a/consensus/polybft/contractsapi/artifacts-gen/main.go b/consensus/polybft/contractsapi/artifacts-gen/main.go new file mode 100644 index 0000000000..4e0567eae4 --- /dev/null +++ b/consensus/polybft/contractsapi/artifacts-gen/main.go @@ -0,0 +1,85 @@ +package main + +import ( + "fmt" + "os" + "path" + "runtime" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi/artifact" + "github.com/dave/jennifer/jen" +) + +func main() { + _, filename, _, _ := runtime.Caller(0) //nolint: dogsled + currentPath := path.Dir(filename) + scpath := path.Join(currentPath, "../../../../core-contracts/artifacts/contracts/") + + f := jen.NewFile("contractsapi") + f.Comment("This is auto-generated file. DO NOT EDIT.") + + readContracts := []struct { + Path string + Name string + }{ + { + "root/CheckpointManager.sol", + "CheckpointManager", + }, + { + "root/ExitHelper.sol", + "ExitHelper", + }, + { + "child/L2StateSender.sol", + "L2StateSender", + }, + { + "common/BLS.sol", + "BLS", + }, + { + "common/BN256G2.sol", + "BN256G2", + }, + { + "child/StateReceiver.sol", + "StateReceiver", + }, + { + "root/StateSender.sol", + "StateSender", + }, + { + "child/ChildValidatorSet.sol", + "ChildValidatorSet", + }, + { + "child/System.sol", + "System", + }, + { + "child/MRC20.sol", + "MRC20", + }, + } + + for _, v := range readContracts { + artifactBytes, err := artifact.ReadArtifactData(scpath, v.Path, v.Name) + if err != nil { + panic(err) + } + + f.Var().Id(v.Name + "Artifact").String().Op("=").Lit(string(artifactBytes)) + } + + fl, err := os.Create(currentPath + "/../gen_sc_data.go") + if err != nil { + panic(err) + } + + _, err = fmt.Fprintf(fl, "%#v", f) + if err != nil { + panic(err) + } +} diff --git a/consensus/polybft/contractsapi/bindings-gen/main.go b/consensus/polybft/contractsapi/bindings-gen/main.go new file mode 100644 index 0000000000..574a9d19a7 --- /dev/null +++ b/consensus/polybft/contractsapi/bindings-gen/main.go @@ -0,0 +1,331 @@ +package main + +import ( + "bytes" + "fmt" + "go/format" + "io/ioutil" + "strconv" + "strings" + "text/template" + + gensc "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi/artifact" + "github.com/umbracle/ethgo/abi" +) + +const ( + abiTypeNameFormat = "var %sABIType = abi.MustNewType(\"%s\")" + eventNameFormat = "%sEvent" + functionNameFormat = "%sFunction" +) + +type generatedData struct { + resultString []string + structs []string +} + +func main() { + cases := []struct { + contractName string + artifact *artifact.Artifact + functions []string + events []string + }{ + { + "StateReceiver", + gensc.StateReceiver, + []string{ + "commit", + "execute", + }, + []string{ + "StateSyncResult", + "NewCommitment", + }, + }, + { + "ChildValidatorSet", + gensc.ChildValidatorSet, + []string{ + "commitEpoch", + }, + []string{}, + }, + { + "StateSender", + gensc.StateSender, + []string{ + "syncState", + }, + []string{ + "StateSynced", + }, + }, + { + "CheckpointManager", + gensc.CheckpointManager, + []string{ + "submit", + "initialize", + }, + []string{}, + }, + } + + generatedData := &generatedData{} + + for _, c := range cases { + for _, method := range c.functions { + generateFunction(generatedData, c.contractName, c.artifact.Abi.Methods[method]) + } + + for _, event := range c.events { + generateEvent(generatedData, c.contractName, c.artifact.Abi.Events[event]) + } + } + + str := `// Code generated by scapi/gen. DO NOT EDIT. +package contractsapi + +import ( + "math/big" + + "github.com/0xPolygon/polygon-edge/types" + "github.com/umbracle/ethgo/abi" + "github.com/umbracle/ethgo" +) + +` + str += strings.Join(generatedData.resultString, "\n") + + output, err := format.Source([]byte(str)) + if err != nil { + fmt.Println(str) + panic(err) + } + + if err := ioutil.WriteFile("./consensus/polybft/contractsapi/contractsapi.go", output, 0600); err != nil { + panic(err) + } +} + +func getInternalType(paramName string, paramAbiType *abi.Type) string { + internalType := paramAbiType.InternalType() + if internalType == "" { + internalType = strings.Title(paramName) + } else { + internalType = strings.TrimSuffix(internalType, "[]") // remove [] if it's struct array + internalType = strings.TrimPrefix(internalType, "struct ") // remove struct prefix + // if struct is taken from an interface (ICheckpoint.Validator), remove interface + parts := strings.Split(internalType, ".") + if len(parts) > 1 { + internalType = parts[1] + } + } + + return internalType +} + +// generateType generates code for structs used in smart contract functions and events +func generateType(generatedData *generatedData, name string, obj *abi.Type, res *[]string) string { + if obj.Kind() != abi.KindTuple { + panic("BUG: Not expected") + } + + internalType := getInternalType(name, obj) + generatedData.structs = append(generatedData.structs, internalType) + + str := []string{ + "type " + internalType + " struct {", + } + + for _, tupleElem := range obj.TupleElems() { + elem := tupleElem.Elem + + var typ string + + if elem.Kind() == abi.KindTuple { + // Struct + typ = generateNestedType(generatedData, tupleElem.Name, elem, res) + } else if elem.Kind() == abi.KindSlice && elem.Elem().Kind() == abi.KindTuple { + // []Struct + typ = "[]" + generateNestedType(generatedData, getInternalType(tupleElem.Name, elem), elem.Elem(), res) + } else if elem.Kind() == abi.KindArray && elem.Elem().Kind() == abi.KindTuple { + // [n]Struct + typ = "[" + strconv.Itoa(elem.Size()) + "]" + + generateNestedType(generatedData, getInternalType(tupleElem.Name, elem), elem.Elem(), res) + } else if elem.Kind() == abi.KindAddress { + // for address use the native `types.Address` type instead of `ethgo.Address`. Note that + // this only works for simple types and not for []address inputs. This is good enough since + // there are no kinds like that in our smart contracts. + typ = "types.Address" + } else { + // for the rest of the types use the go type returned by abi + typ = elem.GoType().String() + } + + // []byte and [n]byte get rendered as []uint68 and [n]uint8, since we do not have any + // uint8 internally in polybft, we can use regexp to replace those values with the + // correct byte representation + typ = strings.Replace(typ, "[32]uint8", "types.Hash", -1) + typ = strings.Replace(typ, "]uint8", "]byte", -1) + + // Trim the leading _ from name if it exists + fieldName := strings.TrimPrefix(tupleElem.Name, "_") + + // Replacement of Id for ID to make the linter happy + fieldName = strings.Title(fieldName) + fieldName = strings.Replace(fieldName, "Id", "ID", -1) + + str = append(str, fmt.Sprintf("%s %s `abi:\"%s\"`", fieldName, typ, tupleElem.Name)) + } + + str = append(str, "}") + *res = append(*res, strings.Join(str, "\n")) + + return internalType +} + +// generateNestedType generates code for nested types found in smart contracts structs +func generateNestedType(generatedData *generatedData, name string, obj *abi.Type, res *[]string) string { + for _, s := range generatedData.structs { + if s == name { + // do not generate the same type again if it's already generated + // this happens when two functions use the same struct type as one of its parameters + return "*" + name + } + } + + result := generateType(generatedData, name, obj, res) + *res = append(*res, fmt.Sprintf(abiTypeNameFormat, result, obj.Format(true))) + *res = append(*res, generateAbiFuncsForNestedType(result)) + + return "*" + result +} + +// generateAbiFuncsForNestedType generates necessary functions for nested types smart contracts interaction +func generateAbiFuncsForNestedType(name string) string { + tmpl := `func ({{.Sig}} *{{.TName}}) EncodeAbi() ([]byte, error) { + return {{.Name}}ABIType.Encode({{.Sig}}) + } + + func ({{.Sig}} *{{.TName}}) DecodeAbi(buf []byte) error { + return decodeStruct({{.Name}}ABIType, buf, &{{.Sig}}) + }` + + title := strings.Title(name) + + inputs := map[string]interface{}{ + "Sig": strings.ToLower(string(name[0])), + "Name": title, + "TName": title, + } + + return renderTmpl(tmpl, inputs) +} + +// generateEvent generates code for smart contract events +func generateEvent(generatedData *generatedData, contractName string, event *abi.Event) { + name := fmt.Sprintf(eventNameFormat, event.Name) + + res := []string{} + generateType(generatedData, name, event.Inputs, &res) + + // write encode/decode functions + tmplStr := ` +{{range .Structs}} + {{.}} +{{ end }} + +func ({{.Sig}} *{{.TName}}) ParseLog(log *ethgo.Log) error { + return decodeEvent({{.ContractName}}.Abi.Events["{{.Name}}"], log, {{.Sig}}) +}` + + inputs := map[string]interface{}{ + "Structs": res, + "Sig": strings.ToLower(string(name[0])), + "Name": event.Name, + "TName": strings.Title(name), + "ContractName": contractName, + } + + generatedData.resultString = append(generatedData.resultString, renderTmpl(tmplStr, inputs)) +} + +// generateFunction generates code for smart contract function and its parameters +func generateFunction(generatedData *generatedData, contractName string, method *abi.Method) { + methodName := method.Name + if methodName == "initialize" { + // most of the contracts have initialize function, which differ in params + // so make them unique somehow + methodName = strings.Title(methodName + contractName) + } + + methodName = fmt.Sprintf(functionNameFormat, methodName) + + res := []string{} + generateType(generatedData, methodName, method.Inputs, &res) + + // write encode/decode functions + tmplStr := ` +{{range .Structs}} + {{.}} +{{ end }} + +func ({{.Sig}} *{{.TName}}) EncodeAbi() ([]byte, error) { + return {{.ContractName}}.Abi.Methods["{{.Name}}"].Encode({{.Sig}}) +} + +func ({{.Sig}} *{{.TName}}) DecodeAbi(buf []byte) error { + return decodeMethod({{.ContractName}}.Abi.Methods["{{.Name}}"], buf, {{.Sig}}) +}` + + methodType := "function " + method.Name + "(" + if len(method.Inputs.TupleElems()) != 0 { + methodType += encodeFuncTuple(method.Inputs) + } + + methodType += ")" + + if len(method.Outputs.TupleElems()) != 0 { + methodType += "(" + encodeFuncTuple(method.Outputs) + ")" + } + + inputs := map[string]interface{}{ + "Structs": res, + "Type": methodType, + "Sig": strings.ToLower(string(methodName[0])), + "Name": method.Name, + "ContractName": contractName, + "TName": strings.Title(methodName), + } + + generatedData.resultString = append(generatedData.resultString, renderTmpl(tmplStr, inputs)) +} + +func renderTmpl(tmplStr string, inputs map[string]interface{}) string { + tmpl, err := template.New("name").Parse(tmplStr) + if err != nil { + panic(fmt.Sprintf("BUG: Failed to load template: %v", err)) + } + + var tpl bytes.Buffer + if err = tmpl.Execute(&tpl, inputs); err != nil { + panic(fmt.Sprintf("BUG: Failed to render template: %v", err)) + } + + return tpl.String() +} + +func encodeFuncTuple(t *abi.Type) string { + if t.Kind() != abi.KindTuple { + panic("BUG: Kind different than tuple not expected") + } + + str := t.Format(true) + str = strings.TrimPrefix(str, "tuple(") + str = strings.TrimSuffix(str, ")") + + return str +} diff --git a/consensus/polybft/contractsapi/contractsapi.go b/consensus/polybft/contractsapi/contractsapi.go new file mode 100644 index 0000000000..21339df666 --- /dev/null +++ b/consensus/polybft/contractsapi/contractsapi.go @@ -0,0 +1,255 @@ +// Code generated by scapi/gen. DO NOT EDIT. +package contractsapi + +import ( + "math/big" + + "github.com/0xPolygon/polygon-edge/types" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/abi" +) + +type StateSyncCommitment struct { + StartID *big.Int `abi:"startId"` + EndID *big.Int `abi:"endId"` + Root types.Hash `abi:"root"` +} + +var StateSyncCommitmentABIType = abi.MustNewType("tuple(uint256 startId,uint256 endId,bytes32 root)") + +func (s *StateSyncCommitment) EncodeAbi() ([]byte, error) { + return StateSyncCommitmentABIType.Encode(s) +} + +func (s *StateSyncCommitment) DecodeAbi(buf []byte) error { + return decodeStruct(StateSyncCommitmentABIType, buf, &s) +} + +type CommitFunction struct { + Commitment *StateSyncCommitment `abi:"commitment"` + Signature []byte `abi:"signature"` + Bitmap []byte `abi:"bitmap"` +} + +func (c *CommitFunction) EncodeAbi() ([]byte, error) { + return StateReceiver.Abi.Methods["commit"].Encode(c) +} + +func (c *CommitFunction) DecodeAbi(buf []byte) error { + return decodeMethod(StateReceiver.Abi.Methods["commit"], buf, c) +} + +type StateSync struct { + ID *big.Int `abi:"id"` + Sender types.Address `abi:"sender"` + Receiver types.Address `abi:"receiver"` + Data []byte `abi:"data"` +} + +var StateSyncABIType = abi.MustNewType("tuple(uint256 id,address sender,address receiver,bytes data)") + +func (s *StateSync) EncodeAbi() ([]byte, error) { + return StateSyncABIType.Encode(s) +} + +func (s *StateSync) DecodeAbi(buf []byte) error { + return decodeStruct(StateSyncABIType, buf, &s) +} + +type ExecuteFunction struct { + Proof []types.Hash `abi:"proof"` + Obj *StateSync `abi:"obj"` +} + +func (e *ExecuteFunction) EncodeAbi() ([]byte, error) { + return StateReceiver.Abi.Methods["execute"].Encode(e) +} + +func (e *ExecuteFunction) DecodeAbi(buf []byte) error { + return decodeMethod(StateReceiver.Abi.Methods["execute"], buf, e) +} + +type StateSyncResultEvent struct { + Counter *big.Int `abi:"counter"` + Status bool `abi:"status"` + Message []byte `abi:"message"` +} + +func (s *StateSyncResultEvent) ParseLog(log *ethgo.Log) error { + return decodeEvent(StateReceiver.Abi.Events["StateSyncResult"], log, s) +} + +type NewCommitmentEvent struct { + StartID *big.Int `abi:"startId"` + EndID *big.Int `abi:"endId"` + Root types.Hash `abi:"root"` +} + +func (n *NewCommitmentEvent) ParseLog(log *ethgo.Log) error { + return decodeEvent(StateReceiver.Abi.Events["NewCommitment"], log, n) +} + +type Epoch struct { + StartBlock *big.Int `abi:"startBlock"` + EndBlock *big.Int `abi:"endBlock"` + EpochRoot types.Hash `abi:"epochRoot"` +} + +var EpochABIType = abi.MustNewType("tuple(uint256 startBlock,uint256 endBlock,bytes32 epochRoot)") + +func (e *Epoch) EncodeAbi() ([]byte, error) { + return EpochABIType.Encode(e) +} + +func (e *Epoch) DecodeAbi(buf []byte) error { + return decodeStruct(EpochABIType, buf, &e) +} + +type UptimeData struct { + Validator types.Address `abi:"validator"` + SignedBlocks *big.Int `abi:"signedBlocks"` +} + +var UptimeDataABIType = abi.MustNewType("tuple(address validator,uint256 signedBlocks)") + +func (u *UptimeData) EncodeAbi() ([]byte, error) { + return UptimeDataABIType.Encode(u) +} + +func (u *UptimeData) DecodeAbi(buf []byte) error { + return decodeStruct(UptimeDataABIType, buf, &u) +} + +type Uptime struct { + EpochID *big.Int `abi:"epochId"` + UptimeData []*UptimeData `abi:"uptimeData"` + TotalBlocks *big.Int `abi:"totalBlocks"` +} + +var UptimeABIType = abi.MustNewType("tuple(uint256 epochId,tuple(address validator,uint256 signedBlocks)[] uptimeData,uint256 totalBlocks)") + +func (u *Uptime) EncodeAbi() ([]byte, error) { + return UptimeABIType.Encode(u) +} + +func (u *Uptime) DecodeAbi(buf []byte) error { + return decodeStruct(UptimeABIType, buf, &u) +} + +type CommitEpochFunction struct { + ID *big.Int `abi:"id"` + Epoch *Epoch `abi:"epoch"` + Uptime *Uptime `abi:"uptime"` +} + +func (c *CommitEpochFunction) EncodeAbi() ([]byte, error) { + return ChildValidatorSet.Abi.Methods["commitEpoch"].Encode(c) +} + +func (c *CommitEpochFunction) DecodeAbi(buf []byte) error { + return decodeMethod(ChildValidatorSet.Abi.Methods["commitEpoch"], buf, c) +} + +type SyncStateFunction struct { + Receiver types.Address `abi:"receiver"` + Data []byte `abi:"data"` +} + +func (s *SyncStateFunction) EncodeAbi() ([]byte, error) { + return StateSender.Abi.Methods["syncState"].Encode(s) +} + +func (s *SyncStateFunction) DecodeAbi(buf []byte) error { + return decodeMethod(StateSender.Abi.Methods["syncState"], buf, s) +} + +type StateSyncedEvent struct { + ID *big.Int `abi:"id"` + Sender types.Address `abi:"sender"` + Receiver types.Address `abi:"receiver"` + Data []byte `abi:"data"` +} + +func (s *StateSyncedEvent) ParseLog(log *ethgo.Log) error { + return decodeEvent(StateSender.Abi.Events["StateSynced"], log, s) +} + +type CheckpointMetadata struct { + BlockHash types.Hash `abi:"blockHash"` + BlockRound *big.Int `abi:"blockRound"` + CurrentValidatorSetHash types.Hash `abi:"currentValidatorSetHash"` +} + +var CheckpointMetadataABIType = abi.MustNewType("tuple(bytes32 blockHash,uint256 blockRound,bytes32 currentValidatorSetHash)") + +func (c *CheckpointMetadata) EncodeAbi() ([]byte, error) { + return CheckpointMetadataABIType.Encode(c) +} + +func (c *CheckpointMetadata) DecodeAbi(buf []byte) error { + return decodeStruct(CheckpointMetadataABIType, buf, &c) +} + +type Checkpoint struct { + Epoch *big.Int `abi:"epoch"` + BlockNumber *big.Int `abi:"blockNumber"` + EventRoot types.Hash `abi:"eventRoot"` +} + +var CheckpointABIType = abi.MustNewType("tuple(uint256 epoch,uint256 blockNumber,bytes32 eventRoot)") + +func (c *Checkpoint) EncodeAbi() ([]byte, error) { + return CheckpointABIType.Encode(c) +} + +func (c *Checkpoint) DecodeAbi(buf []byte) error { + return decodeStruct(CheckpointABIType, buf, &c) +} + +type Validator struct { + Address types.Address `abi:"_address"` + BlsKey [4]*big.Int `abi:"blsKey"` + VotingPower *big.Int `abi:"votingPower"` +} + +var ValidatorABIType = abi.MustNewType("tuple(address _address,uint256[4] blsKey,uint256 votingPower)") + +func (v *Validator) EncodeAbi() ([]byte, error) { + return ValidatorABIType.Encode(v) +} + +func (v *Validator) DecodeAbi(buf []byte) error { + return decodeStruct(ValidatorABIType, buf, &v) +} + +type SubmitFunction struct { + ChainID *big.Int `abi:"chainId"` + CheckpointMetadata *CheckpointMetadata `abi:"checkpointMetadata"` + Checkpoint *Checkpoint `abi:"checkpoint"` + Signature [2]*big.Int `abi:"signature"` + NewValidatorSet []*Validator `abi:"newValidatorSet"` + Bitmap []byte `abi:"bitmap"` +} + +func (s *SubmitFunction) EncodeAbi() ([]byte, error) { + return CheckpointManager.Abi.Methods["submit"].Encode(s) +} + +func (s *SubmitFunction) DecodeAbi(buf []byte) error { + return decodeMethod(CheckpointManager.Abi.Methods["submit"], buf, s) +} + +type InitializeCheckpointManagerFunction struct { + NewBls types.Address `abi:"newBls"` + NewBn256G2 types.Address `abi:"newBn256G2"` + NewDomain types.Hash `abi:"newDomain"` + NewValidatorSet []*Validator `abi:"newValidatorSet"` +} + +func (i *InitializeCheckpointManagerFunction) EncodeAbi() ([]byte, error) { + return CheckpointManager.Abi.Methods["initialize"].Encode(i) +} + +func (i *InitializeCheckpointManagerFunction) DecodeAbi(buf []byte) error { + return decodeMethod(CheckpointManager.Abi.Methods["initialize"], buf, i) +} diff --git a/consensus/polybft/contractsapi/contractsapi_test.go b/consensus/polybft/contractsapi/contractsapi_test.go new file mode 100644 index 0000000000..de0e855b63 --- /dev/null +++ b/consensus/polybft/contractsapi/contractsapi_test.go @@ -0,0 +1,84 @@ +package contractsapi + +import ( + "math/big" + "reflect" + "testing" + + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/require" +) + +type method interface { + EncodeAbi() ([]byte, error) + DecodeAbi(buf []byte) error +} + +func TestEncoding_Method(t *testing.T) { + t.Parallel() + + cases := []method{ + // empty commit + &CommitFunction{ + Commitment: &StateSyncCommitment{ + StartID: big.NewInt(1), + EndID: big.NewInt(1), + Root: types.EmptyRootHash, + }, + Signature: []byte{}, + Bitmap: []byte{}, + }, + // empty commit epoch + &CommitEpochFunction{ + ID: big.NewInt(1), + Epoch: &Epoch{ + StartBlock: big.NewInt(1), + EndBlock: big.NewInt(1), + }, + Uptime: &Uptime{ + EpochID: big.NewInt(1), + UptimeData: []*UptimeData{ + { + Validator: types.Address{0x1}, + SignedBlocks: big.NewInt(1), + }, + }, + TotalBlocks: big.NewInt(1), + }, + }, + } + + for _, c := range cases { + res, err := c.EncodeAbi() + require.NoError(t, err) + + // use reflection to create another type and decode + val := reflect.New(reflect.TypeOf(c).Elem()).Interface() + obj, ok := val.(method) + require.True(t, ok) + + err = obj.DecodeAbi(res) + require.NoError(t, err) + require.Equal(t, obj, c) + } +} + +func TestEncoding_Struct(t *testing.T) { + t.Parallel() + + commitment := &StateSyncCommitment{ + StartID: big.NewInt(1), + EndID: big.NewInt(10), + Root: types.StringToHash("hash"), + } + + encoding, err := commitment.EncodeAbi() + require.NoError(t, err) + + commitmentDecoded := &StateSyncCommitment{} + + require.NoError(t, commitmentDecoded.DecodeAbi(encoding)) + require.Equal(t, commitment.StartID, commitmentDecoded.StartID) + require.Equal(t, commitment.EndID, commitmentDecoded.EndID) + require.Equal(t, commitment.Root, commitmentDecoded.Root) +} diff --git a/consensus/polybft/contractsapi/decoder.go b/consensus/polybft/contractsapi/decoder.go new file mode 100644 index 0000000000..2a3852a5f5 --- /dev/null +++ b/consensus/polybft/contractsapi/decoder.go @@ -0,0 +1,99 @@ +package contractsapi + +import ( + "bytes" + "fmt" + "math/big" + "reflect" + + "github.com/mitchellh/mapstructure" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/abi" +) + +const abiMethodIDLength = 4 + +func decodeEvent(event *abi.Event, log *ethgo.Log, out interface{}) error { + val, err := event.ParseLog(log) + if err != nil { + return err + } + + return decodeImpl(val, out) +} + +func decodeMethod(method *abi.Method, input []byte, out interface{}) error { + if len(input) < abiMethodIDLength { + return fmt.Errorf("invalid method data, len = %d", len(input)) + } + + sig := method.ID() + if !bytes.HasPrefix(input, sig) { + return fmt.Errorf("prefix is not correct") + } + + val, err := abi.Decode(method.Inputs, input[abiMethodIDLength:]) + if err != nil { + return err + } + + return decodeImpl(val, out) +} + +func decodeStruct(t *abi.Type, input []byte, out interface{}) error { + if len(input) < abiMethodIDLength { + return fmt.Errorf("invalid struct data, len = %d", len(input)) + } + + val, err := abi.Decode(t, input) + if err != nil { + return err + } + + return decodeImpl(val, out) +} + +func decodeImpl(input interface{}, out interface{}) error { + metadata := &mapstructure.Metadata{} + dc := &mapstructure.DecoderConfig{ + Result: out, + TagName: "abi", + Metadata: metadata, + DecodeHook: customHook, + } + + ms, err := mapstructure.NewDecoder(dc) + if err != nil { + return err + } + + if err = ms.Decode(input); err != nil { + return err + } + + if len(metadata.Unused) != 0 { + return fmt.Errorf("some keys not used: %v", metadata.Unused) + } + + return nil +} + +var bigTyp = reflect.TypeOf(new(big.Int)) + +func customHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if f == bigTyp && t.Kind() == reflect.Uint64 { + // convert big.Int to uint64 (if possible) + b, ok := data.(*big.Int) + if !ok { + return nil, fmt.Errorf("data not a big.Int") + } + + if !b.IsUint64() { + return nil, fmt.Errorf("cannot format big.Int to uint64") + } + + return b.Uint64(), nil + } + + return data, nil +} diff --git a/consensus/polybft/contractsapi/gen_sc_data.go b/consensus/polybft/contractsapi/gen_sc_data.go new file mode 100644 index 0000000000..f4c147e1cd --- /dev/null +++ b/consensus/polybft/contractsapi/gen_sc_data.go @@ -0,0 +1,13 @@ +package contractsapi + +// This is auto-generated file. DO NOT EDIT. +var CheckpointManagerArtifact string = "{\n \"_format\": \"hh-sol-artifact-1\",\n \"contractName\": \"CheckpointManager\",\n \"sourceName\": \"contracts/root/CheckpointManager.sol\",\n \"abi\": [\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": false,\n \"internalType\": \"uint8\",\n \"name\": \"version\",\n \"type\": \"uint8\"\n }\n ],\n \"name\": \"Initialized\",\n \"type\": \"event\"\n },\n {\n \"inputs\": [],\n \"name\": \"bls\",\n \"outputs\": [\n {\n \"internalType\": \"contract IBLS\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"bn256G2\",\n \"outputs\": [\n {\n \"internalType\": \"contract IBN256G2\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"checkpointBlockNumbers\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"checkpoints\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"epoch\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"blockNumber\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"bytes32\",\n \"name\": \"eventRoot\",\n \"type\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"currentCheckpointBlockNumber\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"currentEpoch\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"currentValidatorSet\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"_address\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"votingPower\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"currentValidatorSetLength\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"domain\",\n \"outputs\": [\n {\n \"internalType\": \"bytes32\",\n \"name\": \"\",\n \"type\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"blockNumber\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"bytes32\",\n \"name\": \"leaf\",\n \"type\": \"bytes32\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"leafIndex\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"bytes32[]\",\n \"name\": \"proof\",\n \"type\": \"bytes32[]\"\n }\n ],\n \"name\": \"getEventMembershipByBlockNumber\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"epoch\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"bytes32\",\n \"name\": \"leaf\",\n \"type\": \"bytes32\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"leafIndex\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"bytes32[]\",\n \"name\": \"proof\",\n \"type\": \"bytes32[]\"\n }\n ],\n \"name\": \"getEventMembershipByEpoch\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"blockNumber\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"getEventRootByBlock\",\n \"outputs\": [\n {\n \"internalType\": \"bytes32\",\n \"name\": \"\",\n \"type\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"contract IBLS\",\n \"name\": \"newBls\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"contract IBN256G2\",\n \"name\": \"newBn256G2\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"bytes32\",\n \"name\": \"newDomain\",\n \"type\": \"bytes32\"\n },\n {\n \"components\": [\n {\n \"internalType\": \"address\",\n \"name\": \"_address\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"uint256[4]\",\n \"name\": \"blsKey\",\n \"type\": \"uint256[4]\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"votingPower\",\n \"type\": \"uint256\"\n }\n ],\n \"internalType\": \"struct ICheckpointManager.Validator[]\",\n \"name\": \"newValidatorSet\",\n \"type\": \"tuple[]\"\n }\n ],\n \"name\": \"initialize\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"chainId\",\n \"type\": \"uint256\"\n },\n {\n \"components\": [\n {\n \"internalType\": \"bytes32\",\n \"name\": \"blockHash\",\n \"type\": \"bytes32\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"blockRound\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"bytes32\",\n \"name\": \"currentValidatorSetHash\",\n \"type\": \"bytes32\"\n }\n ],\n \"internalType\": \"struct ICheckpointManager.CheckpointMetadata\",\n \"name\": \"checkpointMetadata\",\n \"type\": \"tuple\"\n },\n {\n \"components\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"epoch\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"blockNumber\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"bytes32\",\n \"name\": \"eventRoot\",\n \"type\": \"bytes32\"\n }\n ],\n \"internalType\": \"struct ICheckpointManager.Checkpoint\",\n \"name\": \"checkpoint\",\n \"type\": \"tuple\"\n },\n {\n \"internalType\": \"uint256[2]\",\n \"name\": \"signature\",\n \"type\": \"uint256[2]\"\n },\n {\n \"components\": [\n {\n \"internalType\": \"address\",\n \"name\": \"_address\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"uint256[4]\",\n \"name\": \"blsKey\",\n \"type\": \"uint256[4]\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"votingPower\",\n \"type\": \"uint256\"\n }\n ],\n \"internalType\": \"struct ICheckpointManager.Validator[]\",\n \"name\": \"newValidatorSet\",\n \"type\": \"tuple[]\"\n },\n {\n \"internalType\": \"bytes\",\n \"name\": \"bitmap\",\n \"type\": \"bytes\"\n }\n ],\n \"name\": \"submit\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"totalVotingPower\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n }\n ],\n \"bytecode\": \"0x608060405234801561001057600080fd5b50611565806100206000396000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c80637667180811610097578063c2fb26a611610066578063c2fb26a614610260578063d4c8e3e814610269578063e416d6771461027c578063e9193d2b1461028557600080fd5b806376671808146101cf57806395b0b027146101d8578063b8a2425214610203578063bb1853ca1461024d57600080fd5b806361a02208116100d357806361a022081461013e578063671b3793146101615780636969a25c1461016a578063729e7c6e146101bc57600080fd5b80631d1d4f26146100fa5780633569ed93146101165780635036759a14610129575b600080fd5b61010360025481565b6040519081526020015b60405180910390f35b610103610124366004610e7b565b610298565b61013c610137366004610ef8565b6102cc565b005b61015161014c366004610f6b565b610423565b604051901515815260200161010d565b61010360045481565b61019d610178366004610e7b565b600960205260009081526040902080546005909101546001600160a01b039091169082565b604080516001600160a01b03909316835260208301919091520161010d565b6101516101ca366004610f6b565b610496565b61010360015481565b6006546101eb906001600160a01b031681565b6040516001600160a01b03909116815260200161010d565b610232610211366004610e7b565b60086020526000908152604090208054600182015460029092015490919083565b6040805193845260208401929092529082015260600161010d565b61013c61025b366004611013565b6104f5565b61010360055481565b6007546101eb906001600160a01b031681565b61010360035481565b610103610293366004610e7b565b61070b565b60006008816102a8600a8561072c565b6102b390600161110b565b8152602001908152602001600020600201549050919050565b600054610100900460ff16158080156102ec5750600054600160ff909116105b806103065750303b158015610306575060005460ff166001145b61036e5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b6000805460ff191660011790558015610391576000805461ff0019166101001790555b600680546001600160a01b038089166001600160a01b0319928316179092556007805492881692909116919091179055600584905560028290556103d583836107db565b801561041b576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b60008061042f87610298565b90508061047e5760405162461bcd60e51b815260206004820152601e60248201527f4e4f5f4556454e545f524f4f545f464f525f424c4f434b5f4e554d42455200006044820152606401610365565b61048b86868387876108b2565b979650505050505050565b6000858152600860205260408120600201548061047e5760405162461bcd60e51b815260206004820152601760248201527f4e4f5f4556454e545f524f4f545f464f525f45504f43480000000000000000006044820152606401610365565b600088876020013589600001358a602001358a600001358b604001358d604001358b8b60405160200161052992919061111e565b60408051601f198184030181528282528051602091820120908301999099528101969096526060860194909452608085019290925260a084015260c083015260e08201526101008101919091526101200160408051601f198184030181528282528051602091820120908301520160408051601f198184030181529082905260065460055463a850a90960e01b8452919350610628926001600160a01b039091169163a850a909916105e091908690600401611181565b6040805180830381865afa1580156105fc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061062091906111d7565b8785856109cb565b600180546000918261063983611265565b9190505590506106498189610d0b565b8735600081815260086020908152604091829020838155908b01356001820155908a01356002909101558110156106b957600a805460018101825560009190915260208901357fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8909101556106ed565b600a805460208a013591906106d09060019061127e565b815481106106e0576106e0611291565b6000918252602090912001555b60208801356003556106ff86866107db565b50505050505050505050565b600a818154811061071b57600080fd5b600091825260209091200154905081565b8154600090810361073f575060006107d5565b82546000905b8082101561078c5760006107598383610dd2565b6000878152602090209091508590820154111561077857809150610786565b61078381600161110b565b92505b50610745565b6000821180156107b85750836107b5866107a760018661127e565b600091825260209091200190565b54145b156107d1576107c860018361127e565b925050506107d5565b5090505b92915050565b6002819055806000805b828110156108a957600085858381811061080157610801611291565b905060c0020160a001359050600081116108515760405162461bcd60e51b8152602060048201526011602482015270564f54494e475f504f5745525f5a45524f60781b6044820152606401610365565b61085b818461110b565b925085858381811061086f5761086f611291565b905060c0020160096000848152602001908152602001600020818161089491906112be565b90505050806108a290611265565b90506107e5565b50600455505050565b6000816108c0816002611401565b86106109035760405162461bcd60e51b81526020600482015260126024820152710929cac82989288be988a828cbe929c888ab60731b6044820152606401610365565b8660005b828110156109bd57600086868381811061092357610923611291565b9050602002013590506002896109399190611423565b60000361097157604080516020810185905290810182905260600160405160208183030381529060405280519060200120925061099e565b60408051602081018390529081018490526060016040516020818303038152906040528051906020012092505b6109a960028a611437565b985050806109b690611265565b9050610907565b509094149695505050505050565b6002546109d6610e5d565b6000805b83811015610b81576109ed868683610df4565b15610b795781600003610a41576000818152600960205260409081902081516080810190925260010160048282826020028201915b815481526020019060010190808311610a225750505050509250610b5a565b60008181526009602052604080822081516080810190925260010160048282826020028201915b815481526020019060010190808311610a6857505060075488516020808b01516040808d01516060808f01518b51958c0151848d0151928d01519451630cbe96a560e41b81526004810198909852602488019590955260448701929092526064860191909152608485019390935260a484019190915260c483019190915260e48201529495506001600160a01b03169363cbe96a50935061010401915050608060405180830381865afa158015610b23573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b47919061144b565b6060880152604087015260208601528452505b600081815260096020526040902060050154610b76908361110b565b91505b6001016109da565b5080600003610bc45760405162461bcd60e51b815260206004820152600f60248201526e4249544d41505f49535f454d50545960881b6044820152606401610365565b60036004546002610bd591906112a7565b610bdf9190611437565b8111610c2d5760405162461bcd60e51b815260206004820152601960248201527f494e53554646494349454e545f564f54494e475f504f574552000000000000006044820152606401610365565b60065460405163ebbdac9160e01b815260009182916001600160a01b039091169063ebbdac9190610c66908b9088908e90600401611481565b6040805180830381865afa158015610c82573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ca691906114fc565b91509150818015610cb45750805b610d005760405162461bcd60e51b815260206004820152601d60248201527f5349474e41545552455f564552494649434154494f4e5f4641494c45440000006044820152606401610365565b505050505050505050565b600082815260086020908152604091829020825160608101845281548082526001830154938201939093526002909101549281019290925282351015610d835760405162461bcd60e51b815260206004820152600d60248201526c0929cac82989288be8aa09e869609b1b6044820152606401610365565b8060200151826020013511610dcd5760405162461bcd60e51b815260206004820152601060248201526f115354151657d0d21150d2d413d2539560821b6044820152606401610365565b505050565b6000610de16002848418611437565b610ded9084841661110b565b9392505050565b600080610e02600884611437565b90506000610e11600885611423565b9050848210610e2557600092505050610ded565b6000600160ff83161b878785818110610e4057610e40611291565b9050013560f81c60f81b60f81c60ff161611925050509392505050565b60405180608001604052806004906020820280368337509192915050565b600060208284031215610e8d57600080fd5b5035919050565b6001600160a01b0381168114610ea957600080fd5b50565b60008083601f840112610ebe57600080fd5b50813567ffffffffffffffff811115610ed657600080fd5b60208301915083602060c083028501011115610ef157600080fd5b9250929050565b600080600080600060808688031215610f1057600080fd5b8535610f1b81610e94565b94506020860135610f2b81610e94565b935060408601359250606086013567ffffffffffffffff811115610f4e57600080fd5b610f5a88828901610eac565b969995985093965092949392505050565b600080600080600060808688031215610f8357600080fd5b853594506020860135935060408601359250606086013567ffffffffffffffff80821115610fb057600080fd5b818801915088601f830112610fc457600080fd5b813581811115610fd357600080fd5b8960208260051b8501011115610fe857600080fd5b9699959850939650602001949392505050565b60006060828403121561100d57600080fd5b50919050565b600080600080600080600080610160898b03121561103057600080fd5b883597506110418a60208b01610ffb565b96506110508a60808b01610ffb565b955061012089018a81111561106457600080fd5b60e08a0195503567ffffffffffffffff8082111561108157600080fd5b61108d8c838d01610eac565b90965094506101408b01359150808211156110a757600080fd5b818b0191508b601f8301126110bb57600080fd5b8135818111156110ca57600080fd5b8c60208285010111156110dc57600080fd5b6020830194508093505050509295985092959890939650565b634e487b7160e01b600052601160045260246000fd5b808201808211156107d5576107d56110f5565b60208082528181018390526000908460408401835b8681101561117657823561114681610e94565b6001600160a01b0316825260808385018584013760a0838101359083015260c09283019290910190600101611133565b509695505050505050565b82815260006020604081840152835180604085015260005b818110156111b557858101830151858201606001528201611199565b506000606082860101526060601f19601f830116850101925050509392505050565b6000604082840312156111e957600080fd5b82601f8301126111f857600080fd5b6040516040810181811067ffffffffffffffff8211171561122957634e487b7160e01b600052604160045260246000fd5b806040525080604084018581111561124057600080fd5b845b8181101561125a578051835260209283019201611242565b509195945050505050565b600060018201611277576112776110f5565b5060010190565b818103818111156107d5576107d56110f5565b634e487b7160e01b600052603260045260246000fd5b80820281158282048414176107d5576107d56110f5565b81356112c981610e94565b81546001600160a01b0319166001600160a01b0391909116178155602082810160005b600481101561130c578135600182860181019190915591830191016112ec565b50505060a082013560058201555050565b600181815b8085111561135857816000190482111561133e5761133e6110f5565b8085161561134b57918102915b93841c9390800290611322565b509250929050565b60008261136f575060016107d5565b8161137c575060006107d5565b8160018114611392576002811461139c576113b8565b60019150506107d5565b60ff8411156113ad576113ad6110f5565b50506001821b6107d5565b5060208310610133831016604e8410600b84101617156113db575081810a6107d5565b6113e5838361131d565b80600019048211156113f9576113f96110f5565b029392505050565b6000610ded8383611360565b634e487b7160e01b600052601260045260246000fd5b6000826114325761143261140d565b500690565b6000826114465761144661140d565b500490565b6000806000806080858703121561146157600080fd5b505082516020840151604085015160609095015191969095509092509050565b61010081016040858337604082018460005b60048110156114b2578151835260209283019290910190600101611493565b50505060c082018360005b60028110156114dc5781518352602092830192909101906001016114bd565b505050949350505050565b805180151581146114f757600080fd5b919050565b6000806040838503121561150f57600080fd5b611518836114e7565b9150611526602084016114e7565b9050925092905056fea2646970667358221220a0c0838b66ae5561939526edc9c8be8257337a63060f8b3cb9c0f83765c9510364736f6c63430008110033\",\n \"deployedBytecode\": \"0x608060405234801561001057600080fd5b50600436106100f55760003560e01c80637667180811610097578063c2fb26a611610066578063c2fb26a614610260578063d4c8e3e814610269578063e416d6771461027c578063e9193d2b1461028557600080fd5b806376671808146101cf57806395b0b027146101d8578063b8a2425214610203578063bb1853ca1461024d57600080fd5b806361a02208116100d357806361a022081461013e578063671b3793146101615780636969a25c1461016a578063729e7c6e146101bc57600080fd5b80631d1d4f26146100fa5780633569ed93146101165780635036759a14610129575b600080fd5b61010360025481565b6040519081526020015b60405180910390f35b610103610124366004610e7b565b610298565b61013c610137366004610ef8565b6102cc565b005b61015161014c366004610f6b565b610423565b604051901515815260200161010d565b61010360045481565b61019d610178366004610e7b565b600960205260009081526040902080546005909101546001600160a01b039091169082565b604080516001600160a01b03909316835260208301919091520161010d565b6101516101ca366004610f6b565b610496565b61010360015481565b6006546101eb906001600160a01b031681565b6040516001600160a01b03909116815260200161010d565b610232610211366004610e7b565b60086020526000908152604090208054600182015460029092015490919083565b6040805193845260208401929092529082015260600161010d565b61013c61025b366004611013565b6104f5565b61010360055481565b6007546101eb906001600160a01b031681565b61010360035481565b610103610293366004610e7b565b61070b565b60006008816102a8600a8561072c565b6102b390600161110b565b8152602001908152602001600020600201549050919050565b600054610100900460ff16158080156102ec5750600054600160ff909116105b806103065750303b158015610306575060005460ff166001145b61036e5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084015b60405180910390fd5b6000805460ff191660011790558015610391576000805461ff0019166101001790555b600680546001600160a01b038089166001600160a01b0319928316179092556007805492881692909116919091179055600584905560028290556103d583836107db565b801561041b576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b60008061042f87610298565b90508061047e5760405162461bcd60e51b815260206004820152601e60248201527f4e4f5f4556454e545f524f4f545f464f525f424c4f434b5f4e554d42455200006044820152606401610365565b61048b86868387876108b2565b979650505050505050565b6000858152600860205260408120600201548061047e5760405162461bcd60e51b815260206004820152601760248201527f4e4f5f4556454e545f524f4f545f464f525f45504f43480000000000000000006044820152606401610365565b600088876020013589600001358a602001358a600001358b604001358d604001358b8b60405160200161052992919061111e565b60408051601f198184030181528282528051602091820120908301999099528101969096526060860194909452608085019290925260a084015260c083015260e08201526101008101919091526101200160408051601f198184030181528282528051602091820120908301520160408051601f198184030181529082905260065460055463a850a90960e01b8452919350610628926001600160a01b039091169163a850a909916105e091908690600401611181565b6040805180830381865afa1580156105fc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061062091906111d7565b8785856109cb565b600180546000918261063983611265565b9190505590506106498189610d0b565b8735600081815260086020908152604091829020838155908b01356001820155908a01356002909101558110156106b957600a805460018101825560009190915260208901357fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8909101556106ed565b600a805460208a013591906106d09060019061127e565b815481106106e0576106e0611291565b6000918252602090912001555b60208801356003556106ff86866107db565b50505050505050505050565b600a818154811061071b57600080fd5b600091825260209091200154905081565b8154600090810361073f575060006107d5565b82546000905b8082101561078c5760006107598383610dd2565b6000878152602090209091508590820154111561077857809150610786565b61078381600161110b565b92505b50610745565b6000821180156107b85750836107b5866107a760018661127e565b600091825260209091200190565b54145b156107d1576107c860018361127e565b925050506107d5565b5090505b92915050565b6002819055806000805b828110156108a957600085858381811061080157610801611291565b905060c0020160a001359050600081116108515760405162461bcd60e51b8152602060048201526011602482015270564f54494e475f504f5745525f5a45524f60781b6044820152606401610365565b61085b818461110b565b925085858381811061086f5761086f611291565b905060c0020160096000848152602001908152602001600020818161089491906112be565b90505050806108a290611265565b90506107e5565b50600455505050565b6000816108c0816002611401565b86106109035760405162461bcd60e51b81526020600482015260126024820152710929cac82989288be988a828cbe929c888ab60731b6044820152606401610365565b8660005b828110156109bd57600086868381811061092357610923611291565b9050602002013590506002896109399190611423565b60000361097157604080516020810185905290810182905260600160405160208183030381529060405280519060200120925061099e565b60408051602081018390529081018490526060016040516020818303038152906040528051906020012092505b6109a960028a611437565b985050806109b690611265565b9050610907565b509094149695505050505050565b6002546109d6610e5d565b6000805b83811015610b81576109ed868683610df4565b15610b795781600003610a41576000818152600960205260409081902081516080810190925260010160048282826020028201915b815481526020019060010190808311610a225750505050509250610b5a565b60008181526009602052604080822081516080810190925260010160048282826020028201915b815481526020019060010190808311610a6857505060075488516020808b01516040808d01516060808f01518b51958c0151848d0151928d01519451630cbe96a560e41b81526004810198909852602488019590955260448701929092526064860191909152608485019390935260a484019190915260c483019190915260e48201529495506001600160a01b03169363cbe96a50935061010401915050608060405180830381865afa158015610b23573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b47919061144b565b6060880152604087015260208601528452505b600081815260096020526040902060050154610b76908361110b565b91505b6001016109da565b5080600003610bc45760405162461bcd60e51b815260206004820152600f60248201526e4249544d41505f49535f454d50545960881b6044820152606401610365565b60036004546002610bd591906112a7565b610bdf9190611437565b8111610c2d5760405162461bcd60e51b815260206004820152601960248201527f494e53554646494349454e545f564f54494e475f504f574552000000000000006044820152606401610365565b60065460405163ebbdac9160e01b815260009182916001600160a01b039091169063ebbdac9190610c66908b9088908e90600401611481565b6040805180830381865afa158015610c82573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ca691906114fc565b91509150818015610cb45750805b610d005760405162461bcd60e51b815260206004820152601d60248201527f5349474e41545552455f564552494649434154494f4e5f4641494c45440000006044820152606401610365565b505050505050505050565b600082815260086020908152604091829020825160608101845281548082526001830154938201939093526002909101549281019290925282351015610d835760405162461bcd60e51b815260206004820152600d60248201526c0929cac82989288be8aa09e869609b1b6044820152606401610365565b8060200151826020013511610dcd5760405162461bcd60e51b815260206004820152601060248201526f115354151657d0d21150d2d413d2539560821b6044820152606401610365565b505050565b6000610de16002848418611437565b610ded9084841661110b565b9392505050565b600080610e02600884611437565b90506000610e11600885611423565b9050848210610e2557600092505050610ded565b6000600160ff83161b878785818110610e4057610e40611291565b9050013560f81c60f81b60f81c60ff161611925050509392505050565b60405180608001604052806004906020820280368337509192915050565b600060208284031215610e8d57600080fd5b5035919050565b6001600160a01b0381168114610ea957600080fd5b50565b60008083601f840112610ebe57600080fd5b50813567ffffffffffffffff811115610ed657600080fd5b60208301915083602060c083028501011115610ef157600080fd5b9250929050565b600080600080600060808688031215610f1057600080fd5b8535610f1b81610e94565b94506020860135610f2b81610e94565b935060408601359250606086013567ffffffffffffffff811115610f4e57600080fd5b610f5a88828901610eac565b969995985093965092949392505050565b600080600080600060808688031215610f8357600080fd5b853594506020860135935060408601359250606086013567ffffffffffffffff80821115610fb057600080fd5b818801915088601f830112610fc457600080fd5b813581811115610fd357600080fd5b8960208260051b8501011115610fe857600080fd5b9699959850939650602001949392505050565b60006060828403121561100d57600080fd5b50919050565b600080600080600080600080610160898b03121561103057600080fd5b883597506110418a60208b01610ffb565b96506110508a60808b01610ffb565b955061012089018a81111561106457600080fd5b60e08a0195503567ffffffffffffffff8082111561108157600080fd5b61108d8c838d01610eac565b90965094506101408b01359150808211156110a757600080fd5b818b0191508b601f8301126110bb57600080fd5b8135818111156110ca57600080fd5b8c60208285010111156110dc57600080fd5b6020830194508093505050509295985092959890939650565b634e487b7160e01b600052601160045260246000fd5b808201808211156107d5576107d56110f5565b60208082528181018390526000908460408401835b8681101561117657823561114681610e94565b6001600160a01b0316825260808385018584013760a0838101359083015260c09283019290910190600101611133565b509695505050505050565b82815260006020604081840152835180604085015260005b818110156111b557858101830151858201606001528201611199565b506000606082860101526060601f19601f830116850101925050509392505050565b6000604082840312156111e957600080fd5b82601f8301126111f857600080fd5b6040516040810181811067ffffffffffffffff8211171561122957634e487b7160e01b600052604160045260246000fd5b806040525080604084018581111561124057600080fd5b845b8181101561125a578051835260209283019201611242565b509195945050505050565b600060018201611277576112776110f5565b5060010190565b818103818111156107d5576107d56110f5565b634e487b7160e01b600052603260045260246000fd5b80820281158282048414176107d5576107d56110f5565b81356112c981610e94565b81546001600160a01b0319166001600160a01b0391909116178155602082810160005b600481101561130c578135600182860181019190915591830191016112ec565b50505060a082013560058201555050565b600181815b8085111561135857816000190482111561133e5761133e6110f5565b8085161561134b57918102915b93841c9390800290611322565b509250929050565b60008261136f575060016107d5565b8161137c575060006107d5565b8160018114611392576002811461139c576113b8565b60019150506107d5565b60ff8411156113ad576113ad6110f5565b50506001821b6107d5565b5060208310610133831016604e8410600b84101617156113db575081810a6107d5565b6113e5838361131d565b80600019048211156113f9576113f96110f5565b029392505050565b6000610ded8383611360565b634e487b7160e01b600052601260045260246000fd5b6000826114325761143261140d565b500690565b6000826114465761144661140d565b500490565b6000806000806080858703121561146157600080fd5b505082516020840151604085015160609095015191969095509092509050565b61010081016040858337604082018460005b60048110156114b2578151835260209283019290910190600101611493565b50505060c082018360005b60028110156114dc5781518352602092830192909101906001016114bd565b505050949350505050565b805180151581146114f757600080fd5b919050565b6000806040838503121561150f57600080fd5b611518836114e7565b9150611526602084016114e7565b9050925092905056fea2646970667358221220a0c0838b66ae5561939526edc9c8be8257337a63060f8b3cb9c0f83765c9510364736f6c63430008110033\",\n \"linkReferences\": {},\n \"deployedLinkReferences\": {}\n}\n" +var ExitHelperArtifact string = "{\n \"_format\": \"hh-sol-artifact-1\",\n \"contractName\": \"ExitHelper\",\n \"sourceName\": \"contracts/root/ExitHelper.sol\",\n \"abi\": [\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"uint256\",\n \"name\": \"id\",\n \"type\": \"uint256\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"bool\",\n \"name\": \"success\",\n \"type\": \"bool\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"bytes\",\n \"name\": \"returnData\",\n \"type\": \"bytes\"\n }\n ],\n \"name\": \"ExitProcessed\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": false,\n \"internalType\": \"uint8\",\n \"name\": \"version\",\n \"type\": \"uint8\"\n }\n ],\n \"name\": \"Initialized\",\n \"type\": \"event\"\n },\n {\n \"inputs\": [\n {\n \"components\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"blockNumber\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"leafIndex\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"bytes\",\n \"name\": \"unhashedLeaf\",\n \"type\": \"bytes\"\n },\n {\n \"internalType\": \"bytes32[]\",\n \"name\": \"proof\",\n \"type\": \"bytes32[]\"\n }\n ],\n \"internalType\": \"struct IExitHelper.BatchExitInput[]\",\n \"name\": \"inputs\",\n \"type\": \"tuple[]\"\n }\n ],\n \"name\": \"batchExit\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"checkpointManager\",\n \"outputs\": [\n {\n \"internalType\": \"contract ICheckpointManager\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"blockNumber\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"leafIndex\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"bytes\",\n \"name\": \"unhashedLeaf\",\n \"type\": \"bytes\"\n },\n {\n \"internalType\": \"bytes32[]\",\n \"name\": \"proof\",\n \"type\": \"bytes32[]\"\n }\n ],\n \"name\": \"exit\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"contract ICheckpointManager\",\n \"name\": \"newCheckpointManager\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"initialize\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"processedExits\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n }\n ],\n \"bytecode\": \"0x608060405234801561001057600080fd5b50610b6f806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c806350607b351461005c578063aa209cc314610071578063bd88ea7914610084578063c0857ba0146100bc578063c4d66de8146100e7575b600080fd5b61006f61006a36600461070d565b6100fa565b005b61006f61007f36600461074f565b61022c565b6100a7610092366004610800565b60016020526000908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6002546100cf906001600160a01b031681565b6040516001600160a01b0390911681526020016100b3565b61006f6100f5366004610831565b61029c565b6002546001600160a01b03166101575760405162461bcd60e51b815260206004820152601b60248201527f4578697448656c7065723a204e4f545f494e495449414c495a4544000000000060448201526064015b60405180910390fd5b8060005b818110156102265761021e84848381811061017857610178610855565b905060200281019061018a919061086b565b3585858481811061019d5761019d610855565b90506020028101906101af919061086b565b602001358686858181106101c5576101c5610855565b90506020028101906101d7919061086b565b6101e590604081019061088b565b8888878181106101f7576101f7610855565b9050602002810190610209919061086b565b6102179060608101906108d2565b600161042e565b60010161015b565b50505050565b6002546001600160a01b03166102845760405162461bcd60e51b815260206004820152601b60248201527f4578697448656c7065723a204e4f545f494e495449414c495a45440000000000604482015260640161014e565b610294868686868686600061042e565b505050505050565b600054610100900460ff16158080156102bc5750600054600160ff909116105b806102d65750303b1580156102d6575060005460ff166001145b6103395760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161014e565b6000805460ff19166001179055801561035c576000805461ff0019166101001790555b6001600160a01b0382161580159061037d57506001600160a01b0382163b15155b6103c95760405162461bcd60e51b815260206004820152601b60248201527f4578697448656c7065723a20494e56414c49445f414444524553530000000000604482015260640161014e565b600280546001600160a01b0319166001600160a01b038416179055801561042a576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b600080808061043f888a018a610932565b935093509350935084156104725760008481526001602052604090205460ff161561046d57505050506106b8565b6104dc565b60008481526001602052604090205460ff16156104dc5760405162461bcd60e51b815260206004820152602260248201527f4578697448656c7065723a20455849545f414c52454144595f50524f43455353604482015261115160f21b606482015260840161014e565b6002546040516001600160a01b03909116906361a02208908d90610503908d908d90610a12565b6040519081900381206001600160e01b031960e085901b16825261053092918f908d908d90600401610a22565b602060405180830381865afa15801561054d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105719190610a6f565b6105bd5760405162461bcd60e51b815260206004820152601960248201527f4578697448656c7065723a20494e56414c49445f50524f4f4600000000000000604482015260640161014e565b6000848152600160208190526040808320805460ff19169092179091555181906001600160a01b038516906105fa90889088908790602401610ae1565b60408051601f198184030181529181526020820180516001600160e01b031663f43cda8b60e01b1790525161062f9190610b14565b6000604051808303816000865af19150503d806000811461066c576040519150601f19603f3d011682016040523d82523d6000602084013e610671565b606091505b5091509150811515867f8bbfa0c9bee3785c03700d2a909592286efb83fc7e7002be5764424b9842f7ec836040516106a99190610b26565b60405180910390a35050505050505b50505050505050565b60008083601f8401126106d357600080fd5b50813567ffffffffffffffff8111156106eb57600080fd5b6020830191508360208260051b850101111561070657600080fd5b9250929050565b6000806020838503121561072057600080fd5b823567ffffffffffffffff81111561073757600080fd5b610743858286016106c1565b90969095509350505050565b6000806000806000806080878903121561076857600080fd5b8635955060208701359450604087013567ffffffffffffffff8082111561078e57600080fd5b818901915089601f8301126107a257600080fd5b8135818111156107b157600080fd5b8a60208285010111156107c357600080fd5b6020830196508095505060608901359150808211156107e157600080fd5b506107ee89828a016106c1565b979a9699509497509295939492505050565b60006020828403121561081257600080fd5b5035919050565b6001600160a01b038116811461082e57600080fd5b50565b60006020828403121561084357600080fd5b813561084e81610819565b9392505050565b634e487b7160e01b600052603260045260246000fd5b60008235607e1983360301811261088157600080fd5b9190910192915050565b6000808335601e198436030181126108a257600080fd5b83018035915067ffffffffffffffff8211156108bd57600080fd5b60200191503681900382131561070657600080fd5b6000808335601e198436030181126108e957600080fd5b83018035915067ffffffffffffffff82111561090457600080fd5b6020019150600581901b360382131561070657600080fd5b634e487b7160e01b600052604160045260246000fd5b6000806000806080858703121561094857600080fd5b84359350602085013561095a81610819565b9250604085013561096a81610819565b9150606085013567ffffffffffffffff8082111561098757600080fd5b818701915087601f83011261099b57600080fd5b8135818111156109ad576109ad61091c565b604051601f8201601f19908116603f011681019083821181831017156109d5576109d561091c565b816040528281528a60208487010111156109ee57600080fd5b82602086016020830137600060208483010152809550505050505092959194509250565b8183823760009101908152919050565b85815284602082015283604082015260806060820152816080820152600060018060fb1b03831115610a5357600080fd5b8260051b808560a08501379190910160a0019695505050505050565b600060208284031215610a8157600080fd5b8151801515811461084e57600080fd5b60005b83811015610aac578181015183820152602001610a94565b50506000910152565b60008151808452610acd816020860160208601610a91565b601f01601f19169290920160200192915050565b8381526001600160a01b0383166020820152606060408201819052600090610b0b90830184610ab5565b95945050505050565b60008251610881818460208701610a91565b60208152600061084e6020830184610ab556fea2646970667358221220f024c117ff29e95e3e391e2131ae3a5484ff847b61c45d9cfa75dd4b0eef5f8b64736f6c63430008110033\",\n \"deployedBytecode\": \"0x608060405234801561001057600080fd5b50600436106100575760003560e01c806350607b351461005c578063aa209cc314610071578063bd88ea7914610084578063c0857ba0146100bc578063c4d66de8146100e7575b600080fd5b61006f61006a36600461070d565b6100fa565b005b61006f61007f36600461074f565b61022c565b6100a7610092366004610800565b60016020526000908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6002546100cf906001600160a01b031681565b6040516001600160a01b0390911681526020016100b3565b61006f6100f5366004610831565b61029c565b6002546001600160a01b03166101575760405162461bcd60e51b815260206004820152601b60248201527f4578697448656c7065723a204e4f545f494e495449414c495a4544000000000060448201526064015b60405180910390fd5b8060005b818110156102265761021e84848381811061017857610178610855565b905060200281019061018a919061086b565b3585858481811061019d5761019d610855565b90506020028101906101af919061086b565b602001358686858181106101c5576101c5610855565b90506020028101906101d7919061086b565b6101e590604081019061088b565b8888878181106101f7576101f7610855565b9050602002810190610209919061086b565b6102179060608101906108d2565b600161042e565b60010161015b565b50505050565b6002546001600160a01b03166102845760405162461bcd60e51b815260206004820152601b60248201527f4578697448656c7065723a204e4f545f494e495449414c495a45440000000000604482015260640161014e565b610294868686868686600061042e565b505050505050565b600054610100900460ff16158080156102bc5750600054600160ff909116105b806102d65750303b1580156102d6575060005460ff166001145b6103395760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161014e565b6000805460ff19166001179055801561035c576000805461ff0019166101001790555b6001600160a01b0382161580159061037d57506001600160a01b0382163b15155b6103c95760405162461bcd60e51b815260206004820152601b60248201527f4578697448656c7065723a20494e56414c49445f414444524553530000000000604482015260640161014e565b600280546001600160a01b0319166001600160a01b038416179055801561042a576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b600080808061043f888a018a610932565b935093509350935084156104725760008481526001602052604090205460ff161561046d57505050506106b8565b6104dc565b60008481526001602052604090205460ff16156104dc5760405162461bcd60e51b815260206004820152602260248201527f4578697448656c7065723a20455849545f414c52454144595f50524f43455353604482015261115160f21b606482015260840161014e565b6002546040516001600160a01b03909116906361a02208908d90610503908d908d90610a12565b6040519081900381206001600160e01b031960e085901b16825261053092918f908d908d90600401610a22565b602060405180830381865afa15801561054d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105719190610a6f565b6105bd5760405162461bcd60e51b815260206004820152601960248201527f4578697448656c7065723a20494e56414c49445f50524f4f4600000000000000604482015260640161014e565b6000848152600160208190526040808320805460ff19169092179091555181906001600160a01b038516906105fa90889088908790602401610ae1565b60408051601f198184030181529181526020820180516001600160e01b031663f43cda8b60e01b1790525161062f9190610b14565b6000604051808303816000865af19150503d806000811461066c576040519150601f19603f3d011682016040523d82523d6000602084013e610671565b606091505b5091509150811515867f8bbfa0c9bee3785c03700d2a909592286efb83fc7e7002be5764424b9842f7ec836040516106a99190610b26565b60405180910390a35050505050505b50505050505050565b60008083601f8401126106d357600080fd5b50813567ffffffffffffffff8111156106eb57600080fd5b6020830191508360208260051b850101111561070657600080fd5b9250929050565b6000806020838503121561072057600080fd5b823567ffffffffffffffff81111561073757600080fd5b610743858286016106c1565b90969095509350505050565b6000806000806000806080878903121561076857600080fd5b8635955060208701359450604087013567ffffffffffffffff8082111561078e57600080fd5b818901915089601f8301126107a257600080fd5b8135818111156107b157600080fd5b8a60208285010111156107c357600080fd5b6020830196508095505060608901359150808211156107e157600080fd5b506107ee89828a016106c1565b979a9699509497509295939492505050565b60006020828403121561081257600080fd5b5035919050565b6001600160a01b038116811461082e57600080fd5b50565b60006020828403121561084357600080fd5b813561084e81610819565b9392505050565b634e487b7160e01b600052603260045260246000fd5b60008235607e1983360301811261088157600080fd5b9190910192915050565b6000808335601e198436030181126108a257600080fd5b83018035915067ffffffffffffffff8211156108bd57600080fd5b60200191503681900382131561070657600080fd5b6000808335601e198436030181126108e957600080fd5b83018035915067ffffffffffffffff82111561090457600080fd5b6020019150600581901b360382131561070657600080fd5b634e487b7160e01b600052604160045260246000fd5b6000806000806080858703121561094857600080fd5b84359350602085013561095a81610819565b9250604085013561096a81610819565b9150606085013567ffffffffffffffff8082111561098757600080fd5b818701915087601f83011261099b57600080fd5b8135818111156109ad576109ad61091c565b604051601f8201601f19908116603f011681019083821181831017156109d5576109d561091c565b816040528281528a60208487010111156109ee57600080fd5b82602086016020830137600060208483010152809550505050505092959194509250565b8183823760009101908152919050565b85815284602082015283604082015260806060820152816080820152600060018060fb1b03831115610a5357600080fd5b8260051b808560a08501379190910160a0019695505050505050565b600060208284031215610a8157600080fd5b8151801515811461084e57600080fd5b60005b83811015610aac578181015183820152602001610a94565b50506000910152565b60008151808452610acd816020860160208601610a91565b601f01601f19169290920160200192915050565b8381526001600160a01b0383166020820152606060408201819052600090610b0b90830184610ab5565b95945050505050565b60008251610881818460208701610a91565b60208152600061084e6020830184610ab556fea2646970667358221220f024c117ff29e95e3e391e2131ae3a5484ff847b61c45d9cfa75dd4b0eef5f8b64736f6c63430008110033\",\n \"linkReferences\": {},\n \"deployedLinkReferences\": {}\n}\n" +var L2StateSenderArtifact string = "{\n \"_format\": \"hh-sol-artifact-1\",\n \"contractName\": \"L2StateSender\",\n \"sourceName\": \"contracts/child/L2StateSender.sol\",\n \"abi\": [\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"uint256\",\n \"name\": \"id\",\n \"type\": \"uint256\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"sender\",\n \"type\": \"address\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"receiver\",\n \"type\": \"address\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"bytes\",\n \"name\": \"data\",\n \"type\": \"bytes\"\n }\n ],\n \"name\": \"L2StateSynced\",\n \"type\": \"event\"\n },\n {\n \"inputs\": [],\n \"name\": \"counter\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"receiver\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"bytes\",\n \"name\": \"data\",\n \"type\": \"bytes\"\n }\n ],\n \"name\": \"syncState\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n }\n ],\n \"bytecode\": \"0x608060405234801561001057600080fd5b50610239806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806316f198311461003b57806361bc221a14610050575b600080fd5b61004e61004936600461011c565b61006b565b005b61005960005481565b60405190815260200160405180910390f35b6108008111156100b65760405162461bcd60e51b815260206004820152601260248201527108ab0868a8a88a6be9a82b0be988a9c8ea8960731b604482015260640160405180910390fd5b826001600160a01b0316336001600160a01b031660008081546100d8906101ad565b9190508190557fedaf3c471ebd67d60c29efe34b639ede7d6a1d92eaeb3f503e784971e67118a5858560405161010f9291906101d4565b60405180910390a4505050565b60008060006040848603121561013157600080fd5b83356001600160a01b038116811461014857600080fd5b9250602084013567ffffffffffffffff8082111561016557600080fd5b818601915086601f83011261017957600080fd5b81358181111561018857600080fd5b87602082850101111561019a57600080fd5b6020830194508093505050509250925092565b6000600182016101cd57634e487b7160e01b600052601160045260246000fd5b5060010190565b60208152816020820152818360408301376000818301604090810191909152601f909201601f1916010191905056fea264697066735822122025c8b63e0888fc8f500a5ce6bf1553db80a3f3339c5c12f5ba280a7b7466c5f664736f6c63430008110033\",\n \"deployedBytecode\": \"0x608060405234801561001057600080fd5b50600436106100365760003560e01c806316f198311461003b57806361bc221a14610050575b600080fd5b61004e61004936600461011c565b61006b565b005b61005960005481565b60405190815260200160405180910390f35b6108008111156100b65760405162461bcd60e51b815260206004820152601260248201527108ab0868a8a88a6be9a82b0be988a9c8ea8960731b604482015260640160405180910390fd5b826001600160a01b0316336001600160a01b031660008081546100d8906101ad565b9190508190557fedaf3c471ebd67d60c29efe34b639ede7d6a1d92eaeb3f503e784971e67118a5858560405161010f9291906101d4565b60405180910390a4505050565b60008060006040848603121561013157600080fd5b83356001600160a01b038116811461014857600080fd5b9250602084013567ffffffffffffffff8082111561016557600080fd5b818601915086601f83011261017957600080fd5b81358181111561018857600080fd5b87602082850101111561019a57600080fd5b6020830194508093505050509250925092565b6000600182016101cd57634e487b7160e01b600052601160045260246000fd5b5060010190565b60208152816020820152818360408301376000818301604090810191909152601f909201601f1916010191905056fea264697066735822122025c8b63e0888fc8f500a5ce6bf1553db80a3f3339c5c12f5ba280a7b7466c5f664736f6c63430008110033\",\n \"linkReferences\": {},\n \"deployedLinkReferences\": {}\n}\n" +var BLSArtifact string = "{\n \"_format\": \"hh-sol-artifact-1\",\n \"contractName\": \"BLS\",\n \"sourceName\": \"contracts/common/BLS.sol\",\n \"abi\": [\n {\n \"inputs\": [\n {\n \"internalType\": \"bytes32\",\n \"name\": \"domain\",\n \"type\": \"bytes32\"\n },\n {\n \"internalType\": \"bytes\",\n \"name\": \"message\",\n \"type\": \"bytes\"\n }\n ],\n \"name\": \"expandMsgTo96\",\n \"outputs\": [\n {\n \"internalType\": \"bytes\",\n \"name\": \"\",\n \"type\": \"bytes\"\n }\n ],\n \"stateMutability\": \"pure\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"bytes32\",\n \"name\": \"domain\",\n \"type\": \"bytes32\"\n },\n {\n \"internalType\": \"bytes\",\n \"name\": \"messages\",\n \"type\": \"bytes\"\n }\n ],\n \"name\": \"hashToField\",\n \"outputs\": [\n {\n \"internalType\": \"uint256[2]\",\n \"name\": \"\",\n \"type\": \"uint256[2]\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"bytes32\",\n \"name\": \"domain\",\n \"type\": \"bytes32\"\n },\n {\n \"internalType\": \"bytes\",\n \"name\": \"message\",\n \"type\": \"bytes\"\n }\n ],\n \"name\": \"hashToPoint\",\n \"outputs\": [\n {\n \"internalType\": \"uint256[2]\",\n \"name\": \"\",\n \"type\": \"uint256[2]\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256[2]\",\n \"name\": \"point\",\n \"type\": \"uint256[2]\"\n }\n ],\n \"name\": \"isOnCurveG1\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"_isOnCurve\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"pure\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256[4]\",\n \"name\": \"point\",\n \"type\": \"uint256[4]\"\n }\n ],\n \"name\": \"isOnCurveG2\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"_isOnCurve\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"pure\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256[2]\",\n \"name\": \"signature\",\n \"type\": \"uint256[2]\"\n }\n ],\n \"name\": \"isValidSignature\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"_x\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"mapToPoint\",\n \"outputs\": [\n {\n \"internalType\": \"uint256[2]\",\n \"name\": \"p\",\n \"type\": \"uint256[2]\"\n }\n ],\n \"stateMutability\": \"pure\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256[2]\",\n \"name\": \"signature\",\n \"type\": \"uint256[2]\"\n },\n {\n \"internalType\": \"uint256[4][]\",\n \"name\": \"pubkeys\",\n \"type\": \"uint256[4][]\"\n },\n {\n \"internalType\": \"uint256[2][]\",\n \"name\": \"messages\",\n \"type\": \"uint256[2][]\"\n }\n ],\n \"name\": \"verifyMultiple\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"checkResult\",\n \"type\": \"bool\"\n },\n {\n \"internalType\": \"bool\",\n \"name\": \"callSuccess\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256[2]\",\n \"name\": \"signature\",\n \"type\": \"uint256[2]\"\n },\n {\n \"internalType\": \"uint256[4][]\",\n \"name\": \"pubkeys\",\n \"type\": \"uint256[4][]\"\n },\n {\n \"internalType\": \"uint256[2]\",\n \"name\": \"message\",\n \"type\": \"uint256[2]\"\n }\n ],\n \"name\": \"verifyMultipleSameMsg\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"checkResult\",\n \"type\": \"bool\"\n },\n {\n \"internalType\": \"bool\",\n \"name\": \"callSuccess\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256[2]\",\n \"name\": \"signature\",\n \"type\": \"uint256[2]\"\n },\n {\n \"internalType\": \"uint256[4]\",\n \"name\": \"pubkey\",\n \"type\": \"uint256[4]\"\n },\n {\n \"internalType\": \"uint256[2]\",\n \"name\": \"message\",\n \"type\": \"uint256[2]\"\n }\n ],\n \"name\": \"verifySingle\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"\",\n \"type\": \"bool\"\n },\n {\n \"internalType\": \"bool\",\n \"name\": \"\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n }\n ],\n \"bytecode\": \"0x608060405234801561001057600080fd5b50612e62806100206000396000f3fe608060405234801561001057600080fd5b506004361061009e5760003560e01c806391ec2d2b1161006657806391ec2d2b1461013b578063a850a9091461015b578063d58e77331461016e578063e242cce914610181578063ebbdac911461019457600080fd5b8063115000fe146100a3578063247dd9fb146100cb5780633e5476ce146100de5780638669026f146101085780639141376314610128575b600080fd5b6100b66100b13660046127eb565b6101a7565b60405190151581526020015b60405180910390f35b6100b66100d9366004612869565b61030e565b6100f16100ec366004612926565b6103b8565b6040805192151583529015156020830152016100c2565b61011b6101163660046129b5565b6107e8565b6040516100c29190612a3f565b6100f1610136366004612a70565b610904565b61014e6101493660046129b5565b610dee565b6040516100c29190612b6f565b61011b6101693660046129b5565b61108b565b61011b61017c366004612b89565b61126d565b6100b661018f366004612869565b611643565b6100f16101a2366004612ba2565b6116a2565b600081516020830151600080516020612e0d833981519152828309600080516020612e0d8339815191528283098182830101600080516020612e0d8339815191528283840108600080516020612e0d8339815191528682600080516020612e0d83398151915203860109935050600080516020612e0d8339815191528483600080516020612e0d833981519152038301099150600080516020612e0d8339815191527f2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e584089450600080516020612e0d8339815191527e9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2830893506040870151925060608701519150600080516020612e0d8339815191528083600080516020612e0d833981519152038508600080516020612e0d833981519152848608099050600080516020612e0d833981519152828460011b0994149290931491909116949350505050565b8051600090600080516020612e0d83398151915211158061034157506020820151600080516020612e0d83398151915211155b1561034e57506000919050565b60405163e242cce960e01b8152309063e242cce990610371908590600401612a3f565b602060405180830381865afa15801561038e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103b29190612c05565b92915050565b60008083806103e25760405162461bcd60e51b81526004016103d990612c27565b60405180910390fd5b60006103ef826001612c7e565b6103fa906006612c91565b905060008167ffffffffffffffff8111156104175761041761277b565b604051908082528060200260200182016040528015610440578160200160208202803683370190505b50905088600060200201358160008151811061045e5761045e612bef565b602090810291909101015288600160200201358160018151811061048457610484612bef565b6020026020010181815250507f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2816002815181106104c4576104c4612bef565b6020026020010181815250507f1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed8160038151811061050457610504612bef565b6020026020010181815250507f275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec8160048151811061054457610544612bef565b6020026020010181815250507f1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d8160058151811061058457610584612bef565b60200260200101818152505060005b838110156107a3578635826105a9836006612c91565b6105b4906006612c7e565b815181106105c4576105c4612bef565b60209081029190910101528660016020020135826105e3836006612c91565b6105ee906007612c7e565b815181106105fe576105fe612bef565b60200260200101818152505088888281811061061c5761061c612bef565b90506080020160016004811061063457610634612bef565b602002013582610645836006612c91565b610650906008612c7e565b8151811061066057610660612bef565b60200260200101818152505088888281811061067e5761067e612bef565b90506080020160006004811061069657610696612bef565b6020020135826106a7836006612c91565b6106b2906009612c7e565b815181106106c2576106c2612bef565b6020026020010181815250508888828181106106e0576106e0612bef565b9050608002016003600481106106f8576106f8612bef565b602002013582610709836006612c91565b61071490600a612c7e565b8151811061072457610724612bef565b60200260200101818152505088888281811061074257610742612bef565b90506080020160026004811061075a5761075a612bef565b60200201358261076b836006612c91565b61077690600b612c7e565b8151811061078657610786612bef565b60209081029190910101528061079b81612ca8565b915050610593565b506107ac612721565b602081602085026020850160085afa9450846107d25760008095509550505050506107df565b5115159450600193505050505b94509492505050565b6107f061273f565b6040516391ec2d2b60e01b815260009030906391ec2d2b906108189087908790600401612cc1565b600060405180830381865afa158015610835573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261085d9190810190612ce2565b9050600080600080601885016001600160c01b0381511693506030860190506001600160c01b038151169450600080516020612e0d83398151915285600080516020612e0d833981519152600160c01b870908604887015160608801516001600160c01b0390811697501694509250600080516020612e0d83398151915290508481600160c01b860908604080518082019091529283526020830152509695505050505050565b60008084806109255760405162461bcd60e51b81526004016103d990612c27565b8084146109925760405162461bcd60e51b815260206004820152603560248201527f424c533a206e756d626572206f66207075626c6963206b65797320616e64206d604482015274195cdcd859d95cc81b5d5cdd08189948195c5d585b605a1b60648201526084016103d9565b600061099f826001612c7e565b6109aa906006612c91565b905060008167ffffffffffffffff8111156109c7576109c761277b565b6040519080825280602002602001820160405280156109f0578160200160208202803683370190505b509050896000602002013581600081518110610a0e57610a0e612bef565b6020908102919091010152896001602002013581600181518110610a3457610a34612bef565b6020026020010181815250507f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c281600281518110610a7457610a74612bef565b6020026020010181815250507f1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed81600381518110610ab457610ab4612bef565b6020026020010181815250507f275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec81600481518110610af457610af4612bef565b6020026020010181815250507f1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d81600581518110610b3457610b34612bef565b60200260200101818152505060005b83811015610da857878782818110610b5d57610b5d612bef565b905060400201600060028110610b7557610b75612bef565b602002013582610b86836006612c91565b610b91906006612c7e565b81518110610ba157610ba1612bef565b602002602001018181525050878782818110610bbf57610bbf612bef565b905060400201600160028110610bd757610bd7612bef565b602002013582610be8836006612c91565b610bf3906007612c7e565b81518110610c0357610c03612bef565b602002602001018181525050898982818110610c2157610c21612bef565b905060800201600160048110610c3957610c39612bef565b602002013582610c4a836006612c91565b610c55906008612c7e565b81518110610c6557610c65612bef565b602002602001018181525050898982818110610c8357610c83612bef565b905060800201600060048110610c9b57610c9b612bef565b602002013582610cac836006612c91565b610cb7906009612c7e565b81518110610cc757610cc7612bef565b602002602001018181525050898982818110610ce557610ce5612bef565b905060800201600360048110610cfd57610cfd612bef565b602002013582610d0e836006612c91565b610d1990600a612c7e565b81518110610d2957610d29612bef565b602002602001018181525050898982818110610d4757610d47612bef565b905060800201600260048110610d5f57610d5f612bef565b602002013582610d70836006612c91565b610d7b90600b612c7e565b81518110610d8b57610d8b612bef565b602090810291909101015280610da081612ca8565b915050610b43565b50610db1612721565b602081602085026020850160085afa945084610dd7576000809550955050505050610de4565b5115159450600193505050505b9550959350505050565b80516060906000610e00826020612c7e565b610e0b906040612c7e565b610e16906004612c7e565b67ffffffffffffffff811115610e2e57610e2e61277b565b6040519080825280601f01601f191660200182016040528015610e58576020820181803683370190505b5060408051606080825260808201909252919250600091906020820181803683370190505090506060820160005b84811015610ea05760208188018101518383015201610e86565b5083016000815360010160608153600101600081536001810187905260210160208153506000600283604051610ed69190612d59565b602060405180830381855afa158015610ef3573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610f169190612d75565b9050600060429450848452816020850152600160408501536041840188905260206061850153600284604051610f4c9190612d59565b602060405180830381855afa158015610f69573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610f8c9190612d75565b905080602084015280821880602086015250600260408501536041840188905260206061850153600284604051610fc39190612d59565b602060405180830381855afa158015610fe0573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906110039190612d75565b90508060408401528082188060208601525060036040850153604184018890526020606185015360028460405161103a9190612d59565b602060405180830381855afa158015611057573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061107a9190612d75565b606084015250909695505050505050565b61109361273f565b604051638669026f60e01b81526000903090638669026f906110bb9087908790600401612cc1565b6040805180830381865afa1580156110d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110fb9190612d8e565b805160405163d58e773360e01b81526004810191909152909150600090309063d58e7733906024016040805180830381865afa15801561113f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111639190612d8e565b602083015160405163d58e773360e01b81526004810191909152909150600090309063d58e7733906024016040805180830381865afa1580156111aa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111ce9190612d8e565b90506111d861275d565b825181526020808401518282015282516040808401919091529083015160608301526000908460808460066107d05a03fa9050808061121357fe5b50806112615760405162461bcd60e51b815260206004820152601760248201527f424c533a20626e206164642063616c6c206661696c656400000000000000000060448201526064016103d9565b50919695505050505050565b61127561273f565b600080516020612e0d83398151915282106112de5760405162461bcd60e51b815260206004820152602360248201527f6d6170546f506f696e7446543a20696e76616c6964206669656c6420656c656d604482015262195b9d60ea1b60648201526084016103d9565b8160006112ea82611871565b9150506000600080516020612e0d8339815191528061130b5761130b612de3565b8384099050600080516020612e0d8339815191526004820890506000600080516020612e0d83398151915277b3c4d79d41a91759a9e4c7e359b6b89eaec68e62effffffd850990506000600080516020612e0d83398151915283830990506113728161189a565b9050600080516020612e0d8339815191528283099150600080516020612e0d8339815191528183099150600080516020612e0d8339815191528286099150600080516020612e0d8339815191526113d783600080516020612e0d833981519152612df9565b7759e26bcea0d48bacd4f263f1acdb5c4f5763473177fffffe089450600080516020612e0d8339815191528586099150600080516020612e0d8339815191528583099150600080516020612e0d833981519152600383089150600061143b83611871565b9093509050801561148357846114665761146383600080516020612e0d833981519152612df9565b92505b505060408051808201909152938452602084015250909392505050565b600080516020612e0d833981519152600187086114ae90600080516020612e0d833981519152612df9565b9550600080516020612e0d8339815191528687099250600080516020612e0d8339815191528684099250600080516020612e0d8339815191526003840892506114f683611871565b9093509050801561151e57846114665761146383600080516020612e0d833981519152612df9565b600080516020612e0d8339815191528485099550600080516020612e0d8339815191528687099550600080516020612e0d8339815191528287099550600080516020612e0d8339815191528287099550600080516020612e0d833981519152600187089550600080516020612e0d8339815191528687099250600080516020612e0d8339815191528684099250600080516020612e0d8339815191526003840892506115c983611871565b9093509050806116265760405162461bcd60e51b815260206004820152602260248201527f424c533a20626164206674206d617070696e6720696d706c656d656e7461746960448201526137b760f11b60648201526084016103d9565b846114665761146383600080516020612e0d833981519152612df9565b600081516020830151600080516020612e0d833981519152828309600080516020612e0d8339815191528382099050600080516020612e0d833981519152600382089050600080516020612e0d83398151915282830914949350505050565b6000806000604051806101800160405280876000600281106116c6576116c6612bef565b60200201358152602001876001600281106116e3576116e3612bef565b602002013581526020017f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c281526020017f1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed81526020017f275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec81526020017f1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d81526020018560006002811061179857611798612bef565b60200201358152602001856001600281106117b5576117b5612bef565b60200201358152602001866001600481106117d2576117d2612bef565b60200201358152602001866000600481106117ef576117ef612bef565b602002013581526020018660036004811061180c5761180c612bef565b602002013581526020018660026004811061182957611829612bef565b60200201359052905061183a612721565b60006020826101808560085afa90508061185d5760008094509450505050611869565b50511515925060019150505b935093915050565b60008061187d836118a5565b915082600080516020612e0d833981519152838409149050915091565b60006103b282611fda565b6000600080516020612e0d8339815191528083840991508083830981838209828283098385830984848309858484098684850997508684840987858409945087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087838a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985050868889099750868889099750868889099750868889099750868889099750868889099750868489099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868689099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868189099750508587880996508587880996508587880996508585880996508587880996508587880996508587880996508585880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508583880996508587880996508587880996508587880996508587880996508581880996508587880996508587880996508587880996508587880996508583880996508587880996508587880996508587880996508584880996508587880996508587880996508587880996508587880996508587880996508581880996505050505050808283099392505050565b6000600080516020612e0d8339815191528083840991508083830981838209828283098385830984848309858484098684850997508684840987858409945087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087838a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985050868889099750868889099750868889099750868889099750868889099750868889099750868489099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868689099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868189099750508587880996508587880996508587880996508585880996508587880996508587880996508587880996508585880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508583880996508587880996508587880996508587880996508587880996508581880996505050838586099450838586099450838586099450838586099450838186099450508284850993508284850993508284850993508281850993508284850993508284850993508285850993508284850993508284850993508284850993508284850993508284850993508284850993508281850995945050505050565b60405180602001604052806001906020820280368337509192915050565b60405180604001604052806002906020820280368337509192915050565b60405180608001604052806004906020820280368337509192915050565b634e487b7160e01b600052604160045260246000fd5b6040805190810167ffffffffffffffff811182821017156127b4576127b461277b565b60405290565b604051601f8201601f1916810167ffffffffffffffff811182821017156127e3576127e361277b565b604052919050565b6000608082840312156127fd57600080fd5b82601f83011261280c57600080fd5b6040516080810181811067ffffffffffffffff8211171561282f5761282f61277b565b60405280608084018581111561284457600080fd5b845b8181101561285e578035835260209283019201612846565b509195945050505050565b60006040828403121561287b57600080fd5b82601f83011261288a57600080fd5b612892612791565b8060408401858111156128a457600080fd5b845b818110156128be5780358452602093840193016128a6565b509095945050505050565b80604081018310156103b257600080fd5b60008083601f8401126128ec57600080fd5b50813567ffffffffffffffff81111561290457600080fd5b6020830191508360208260071b850101111561291f57600080fd5b9250929050565b60008060008060a0858703121561293c57600080fd5b61294686866128c9565b9350604085013567ffffffffffffffff81111561296257600080fd5b61296e878288016128da565b9094509250612982905086606087016128c9565b905092959194509250565b600067ffffffffffffffff8211156129a7576129a761277b565b50601f01601f191660200190565b600080604083850312156129c857600080fd5b82359150602083013567ffffffffffffffff8111156129e657600080fd5b8301601f810185136129f757600080fd5b8035612a0a612a058261298d565b6127ba565b818152866020838501011115612a1f57600080fd5b816020840160208301376000602083830101528093505050509250929050565b60408101818360005b6002811015612a67578151835260209283019290910190600101612a48565b50505092915050565b600080600080600060808688031215612a8857600080fd5b612a9287876128c9565b9450604086013567ffffffffffffffff80821115612aaf57600080fd5b612abb89838a016128da565b90965094506060880135915080821115612ad457600080fd5b818801915088601f830112612ae857600080fd5b813581811115612af757600080fd5b8960208260061b8501011115612b0c57600080fd5b9699959850939650602001949392505050565b60005b83811015612b3a578181015183820152602001612b22565b50506000910152565b60008151808452612b5b816020860160208601612b1f565b601f01601f19169290920160200192915050565b602081526000612b826020830184612b43565b9392505050565b600060208284031215612b9b57600080fd5b5035919050565b60008060006101008486031215612bb857600080fd5b612bc285856128c9565b925060c0840185811115612bd557600080fd5b604085019250612be586826128c9565b9150509250925092565b634e487b7160e01b600052603260045260246000fd5b600060208284031215612c1757600080fd5b81518015158114612b8257600080fd5b60208082526021908201527f424c533a206e756d626572206f66207075626c6963206b6579206973207a65726040820152606f60f81b606082015260800190565b634e487b7160e01b600052601160045260246000fd5b808201808211156103b2576103b2612c68565b80820281158282048414176103b2576103b2612c68565b600060018201612cba57612cba612c68565b5060010190565b828152604060208201526000612cda6040830184612b43565b949350505050565b600060208284031215612cf457600080fd5b815167ffffffffffffffff811115612d0b57600080fd5b8201601f81018413612d1c57600080fd5b8051612d2a612a058261298d565b818152856020838501011115612d3f57600080fd5b612d50826020830160208601612b1f565b95945050505050565b60008251612d6b818460208701612b1f565b9190910192915050565b600060208284031215612d8757600080fd5b5051919050565b600060408284031215612da057600080fd5b82601f830112612daf57600080fd5b612db7612791565b806040840185811115612dc957600080fd5b845b818110156128be578051845260209384019301612dcb565b634e487b7160e01b600052601260045260246000fd5b818103818111156103b2576103b2612c6856fe30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47a2646970667358221220046fd04cf652b14b5d4629e14b0659a560d128e873764c278e0250d47ac47cde64736f6c63430008110033\",\n \"deployedBytecode\": \"0x608060405234801561001057600080fd5b506004361061009e5760003560e01c806391ec2d2b1161006657806391ec2d2b1461013b578063a850a9091461015b578063d58e77331461016e578063e242cce914610181578063ebbdac911461019457600080fd5b8063115000fe146100a3578063247dd9fb146100cb5780633e5476ce146100de5780638669026f146101085780639141376314610128575b600080fd5b6100b66100b13660046127eb565b6101a7565b60405190151581526020015b60405180910390f35b6100b66100d9366004612869565b61030e565b6100f16100ec366004612926565b6103b8565b6040805192151583529015156020830152016100c2565b61011b6101163660046129b5565b6107e8565b6040516100c29190612a3f565b6100f1610136366004612a70565b610904565b61014e6101493660046129b5565b610dee565b6040516100c29190612b6f565b61011b6101693660046129b5565b61108b565b61011b61017c366004612b89565b61126d565b6100b661018f366004612869565b611643565b6100f16101a2366004612ba2565b6116a2565b600081516020830151600080516020612e0d833981519152828309600080516020612e0d8339815191528283098182830101600080516020612e0d8339815191528283840108600080516020612e0d8339815191528682600080516020612e0d83398151915203860109935050600080516020612e0d8339815191528483600080516020612e0d833981519152038301099150600080516020612e0d8339815191527f2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e584089450600080516020612e0d8339815191527e9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2830893506040870151925060608701519150600080516020612e0d8339815191528083600080516020612e0d833981519152038508600080516020612e0d833981519152848608099050600080516020612e0d833981519152828460011b0994149290931491909116949350505050565b8051600090600080516020612e0d83398151915211158061034157506020820151600080516020612e0d83398151915211155b1561034e57506000919050565b60405163e242cce960e01b8152309063e242cce990610371908590600401612a3f565b602060405180830381865afa15801561038e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103b29190612c05565b92915050565b60008083806103e25760405162461bcd60e51b81526004016103d990612c27565b60405180910390fd5b60006103ef826001612c7e565b6103fa906006612c91565b905060008167ffffffffffffffff8111156104175761041761277b565b604051908082528060200260200182016040528015610440578160200160208202803683370190505b50905088600060200201358160008151811061045e5761045e612bef565b602090810291909101015288600160200201358160018151811061048457610484612bef565b6020026020010181815250507f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2816002815181106104c4576104c4612bef565b6020026020010181815250507f1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed8160038151811061050457610504612bef565b6020026020010181815250507f275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec8160048151811061054457610544612bef565b6020026020010181815250507f1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d8160058151811061058457610584612bef565b60200260200101818152505060005b838110156107a3578635826105a9836006612c91565b6105b4906006612c7e565b815181106105c4576105c4612bef565b60209081029190910101528660016020020135826105e3836006612c91565b6105ee906007612c7e565b815181106105fe576105fe612bef565b60200260200101818152505088888281811061061c5761061c612bef565b90506080020160016004811061063457610634612bef565b602002013582610645836006612c91565b610650906008612c7e565b8151811061066057610660612bef565b60200260200101818152505088888281811061067e5761067e612bef565b90506080020160006004811061069657610696612bef565b6020020135826106a7836006612c91565b6106b2906009612c7e565b815181106106c2576106c2612bef565b6020026020010181815250508888828181106106e0576106e0612bef565b9050608002016003600481106106f8576106f8612bef565b602002013582610709836006612c91565b61071490600a612c7e565b8151811061072457610724612bef565b60200260200101818152505088888281811061074257610742612bef565b90506080020160026004811061075a5761075a612bef565b60200201358261076b836006612c91565b61077690600b612c7e565b8151811061078657610786612bef565b60209081029190910101528061079b81612ca8565b915050610593565b506107ac612721565b602081602085026020850160085afa9450846107d25760008095509550505050506107df565b5115159450600193505050505b94509492505050565b6107f061273f565b6040516391ec2d2b60e01b815260009030906391ec2d2b906108189087908790600401612cc1565b600060405180830381865afa158015610835573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261085d9190810190612ce2565b9050600080600080601885016001600160c01b0381511693506030860190506001600160c01b038151169450600080516020612e0d83398151915285600080516020612e0d833981519152600160c01b870908604887015160608801516001600160c01b0390811697501694509250600080516020612e0d83398151915290508481600160c01b860908604080518082019091529283526020830152509695505050505050565b60008084806109255760405162461bcd60e51b81526004016103d990612c27565b8084146109925760405162461bcd60e51b815260206004820152603560248201527f424c533a206e756d626572206f66207075626c6963206b65797320616e64206d604482015274195cdcd859d95cc81b5d5cdd08189948195c5d585b605a1b60648201526084016103d9565b600061099f826001612c7e565b6109aa906006612c91565b905060008167ffffffffffffffff8111156109c7576109c761277b565b6040519080825280602002602001820160405280156109f0578160200160208202803683370190505b509050896000602002013581600081518110610a0e57610a0e612bef565b6020908102919091010152896001602002013581600181518110610a3457610a34612bef565b6020026020010181815250507f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c281600281518110610a7457610a74612bef565b6020026020010181815250507f1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed81600381518110610ab457610ab4612bef565b6020026020010181815250507f275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec81600481518110610af457610af4612bef565b6020026020010181815250507f1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d81600581518110610b3457610b34612bef565b60200260200101818152505060005b83811015610da857878782818110610b5d57610b5d612bef565b905060400201600060028110610b7557610b75612bef565b602002013582610b86836006612c91565b610b91906006612c7e565b81518110610ba157610ba1612bef565b602002602001018181525050878782818110610bbf57610bbf612bef565b905060400201600160028110610bd757610bd7612bef565b602002013582610be8836006612c91565b610bf3906007612c7e565b81518110610c0357610c03612bef565b602002602001018181525050898982818110610c2157610c21612bef565b905060800201600160048110610c3957610c39612bef565b602002013582610c4a836006612c91565b610c55906008612c7e565b81518110610c6557610c65612bef565b602002602001018181525050898982818110610c8357610c83612bef565b905060800201600060048110610c9b57610c9b612bef565b602002013582610cac836006612c91565b610cb7906009612c7e565b81518110610cc757610cc7612bef565b602002602001018181525050898982818110610ce557610ce5612bef565b905060800201600360048110610cfd57610cfd612bef565b602002013582610d0e836006612c91565b610d1990600a612c7e565b81518110610d2957610d29612bef565b602002602001018181525050898982818110610d4757610d47612bef565b905060800201600260048110610d5f57610d5f612bef565b602002013582610d70836006612c91565b610d7b90600b612c7e565b81518110610d8b57610d8b612bef565b602090810291909101015280610da081612ca8565b915050610b43565b50610db1612721565b602081602085026020850160085afa945084610dd7576000809550955050505050610de4565b5115159450600193505050505b9550959350505050565b80516060906000610e00826020612c7e565b610e0b906040612c7e565b610e16906004612c7e565b67ffffffffffffffff811115610e2e57610e2e61277b565b6040519080825280601f01601f191660200182016040528015610e58576020820181803683370190505b5060408051606080825260808201909252919250600091906020820181803683370190505090506060820160005b84811015610ea05760208188018101518383015201610e86565b5083016000815360010160608153600101600081536001810187905260210160208153506000600283604051610ed69190612d59565b602060405180830381855afa158015610ef3573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610f169190612d75565b9050600060429450848452816020850152600160408501536041840188905260206061850153600284604051610f4c9190612d59565b602060405180830381855afa158015610f69573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610f8c9190612d75565b905080602084015280821880602086015250600260408501536041840188905260206061850153600284604051610fc39190612d59565b602060405180830381855afa158015610fe0573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906110039190612d75565b90508060408401528082188060208601525060036040850153604184018890526020606185015360028460405161103a9190612d59565b602060405180830381855afa158015611057573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061107a9190612d75565b606084015250909695505050505050565b61109361273f565b604051638669026f60e01b81526000903090638669026f906110bb9087908790600401612cc1565b6040805180830381865afa1580156110d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110fb9190612d8e565b805160405163d58e773360e01b81526004810191909152909150600090309063d58e7733906024016040805180830381865afa15801561113f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111639190612d8e565b602083015160405163d58e773360e01b81526004810191909152909150600090309063d58e7733906024016040805180830381865afa1580156111aa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111ce9190612d8e565b90506111d861275d565b825181526020808401518282015282516040808401919091529083015160608301526000908460808460066107d05a03fa9050808061121357fe5b50806112615760405162461bcd60e51b815260206004820152601760248201527f424c533a20626e206164642063616c6c206661696c656400000000000000000060448201526064016103d9565b50919695505050505050565b61127561273f565b600080516020612e0d83398151915282106112de5760405162461bcd60e51b815260206004820152602360248201527f6d6170546f506f696e7446543a20696e76616c6964206669656c6420656c656d604482015262195b9d60ea1b60648201526084016103d9565b8160006112ea82611871565b9150506000600080516020612e0d8339815191528061130b5761130b612de3565b8384099050600080516020612e0d8339815191526004820890506000600080516020612e0d83398151915277b3c4d79d41a91759a9e4c7e359b6b89eaec68e62effffffd850990506000600080516020612e0d83398151915283830990506113728161189a565b9050600080516020612e0d8339815191528283099150600080516020612e0d8339815191528183099150600080516020612e0d8339815191528286099150600080516020612e0d8339815191526113d783600080516020612e0d833981519152612df9565b7759e26bcea0d48bacd4f263f1acdb5c4f5763473177fffffe089450600080516020612e0d8339815191528586099150600080516020612e0d8339815191528583099150600080516020612e0d833981519152600383089150600061143b83611871565b9093509050801561148357846114665761146383600080516020612e0d833981519152612df9565b92505b505060408051808201909152938452602084015250909392505050565b600080516020612e0d833981519152600187086114ae90600080516020612e0d833981519152612df9565b9550600080516020612e0d8339815191528687099250600080516020612e0d8339815191528684099250600080516020612e0d8339815191526003840892506114f683611871565b9093509050801561151e57846114665761146383600080516020612e0d833981519152612df9565b600080516020612e0d8339815191528485099550600080516020612e0d8339815191528687099550600080516020612e0d8339815191528287099550600080516020612e0d8339815191528287099550600080516020612e0d833981519152600187089550600080516020612e0d8339815191528687099250600080516020612e0d8339815191528684099250600080516020612e0d8339815191526003840892506115c983611871565b9093509050806116265760405162461bcd60e51b815260206004820152602260248201527f424c533a20626164206674206d617070696e6720696d706c656d656e7461746960448201526137b760f11b60648201526084016103d9565b846114665761146383600080516020612e0d833981519152612df9565b600081516020830151600080516020612e0d833981519152828309600080516020612e0d8339815191528382099050600080516020612e0d833981519152600382089050600080516020612e0d83398151915282830914949350505050565b6000806000604051806101800160405280876000600281106116c6576116c6612bef565b60200201358152602001876001600281106116e3576116e3612bef565b602002013581526020017f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c281526020017f1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed81526020017f275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec81526020017f1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d81526020018560006002811061179857611798612bef565b60200201358152602001856001600281106117b5576117b5612bef565b60200201358152602001866001600481106117d2576117d2612bef565b60200201358152602001866000600481106117ef576117ef612bef565b602002013581526020018660036004811061180c5761180c612bef565b602002013581526020018660026004811061182957611829612bef565b60200201359052905061183a612721565b60006020826101808560085afa90508061185d5760008094509450505050611869565b50511515925060019150505b935093915050565b60008061187d836118a5565b915082600080516020612e0d833981519152838409149050915091565b60006103b282611fda565b6000600080516020612e0d8339815191528083840991508083830981838209828283098385830984848309858484098684850997508684840987858409945087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087838a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985050868889099750868889099750868889099750868889099750868889099750868889099750868489099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868689099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868189099750508587880996508587880996508587880996508585880996508587880996508587880996508587880996508585880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508583880996508587880996508587880996508587880996508587880996508581880996508587880996508587880996508587880996508587880996508583880996508587880996508587880996508587880996508584880996508587880996508587880996508587880996508587880996508587880996508581880996505050505050808283099392505050565b6000600080516020612e0d8339815191528083840991508083830981838209828283098385830984848309858484098684850997508684840987858409945087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087838a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985050868889099750868889099750868889099750868889099750868889099750868889099750868489099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868689099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868189099750508587880996508587880996508587880996508585880996508587880996508587880996508587880996508585880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508583880996508587880996508587880996508587880996508587880996508581880996505050838586099450838586099450838586099450838586099450838186099450508284850993508284850993508284850993508281850993508284850993508284850993508285850993508284850993508284850993508284850993508284850993508284850993508284850993508281850995945050505050565b60405180602001604052806001906020820280368337509192915050565b60405180604001604052806002906020820280368337509192915050565b60405180608001604052806004906020820280368337509192915050565b634e487b7160e01b600052604160045260246000fd5b6040805190810167ffffffffffffffff811182821017156127b4576127b461277b565b60405290565b604051601f8201601f1916810167ffffffffffffffff811182821017156127e3576127e361277b565b604052919050565b6000608082840312156127fd57600080fd5b82601f83011261280c57600080fd5b6040516080810181811067ffffffffffffffff8211171561282f5761282f61277b565b60405280608084018581111561284457600080fd5b845b8181101561285e578035835260209283019201612846565b509195945050505050565b60006040828403121561287b57600080fd5b82601f83011261288a57600080fd5b612892612791565b8060408401858111156128a457600080fd5b845b818110156128be5780358452602093840193016128a6565b509095945050505050565b80604081018310156103b257600080fd5b60008083601f8401126128ec57600080fd5b50813567ffffffffffffffff81111561290457600080fd5b6020830191508360208260071b850101111561291f57600080fd5b9250929050565b60008060008060a0858703121561293c57600080fd5b61294686866128c9565b9350604085013567ffffffffffffffff81111561296257600080fd5b61296e878288016128da565b9094509250612982905086606087016128c9565b905092959194509250565b600067ffffffffffffffff8211156129a7576129a761277b565b50601f01601f191660200190565b600080604083850312156129c857600080fd5b82359150602083013567ffffffffffffffff8111156129e657600080fd5b8301601f810185136129f757600080fd5b8035612a0a612a058261298d565b6127ba565b818152866020838501011115612a1f57600080fd5b816020840160208301376000602083830101528093505050509250929050565b60408101818360005b6002811015612a67578151835260209283019290910190600101612a48565b50505092915050565b600080600080600060808688031215612a8857600080fd5b612a9287876128c9565b9450604086013567ffffffffffffffff80821115612aaf57600080fd5b612abb89838a016128da565b90965094506060880135915080821115612ad457600080fd5b818801915088601f830112612ae857600080fd5b813581811115612af757600080fd5b8960208260061b8501011115612b0c57600080fd5b9699959850939650602001949392505050565b60005b83811015612b3a578181015183820152602001612b22565b50506000910152565b60008151808452612b5b816020860160208601612b1f565b601f01601f19169290920160200192915050565b602081526000612b826020830184612b43565b9392505050565b600060208284031215612b9b57600080fd5b5035919050565b60008060006101008486031215612bb857600080fd5b612bc285856128c9565b925060c0840185811115612bd557600080fd5b604085019250612be586826128c9565b9150509250925092565b634e487b7160e01b600052603260045260246000fd5b600060208284031215612c1757600080fd5b81518015158114612b8257600080fd5b60208082526021908201527f424c533a206e756d626572206f66207075626c6963206b6579206973207a65726040820152606f60f81b606082015260800190565b634e487b7160e01b600052601160045260246000fd5b808201808211156103b2576103b2612c68565b80820281158282048414176103b2576103b2612c68565b600060018201612cba57612cba612c68565b5060010190565b828152604060208201526000612cda6040830184612b43565b949350505050565b600060208284031215612cf457600080fd5b815167ffffffffffffffff811115612d0b57600080fd5b8201601f81018413612d1c57600080fd5b8051612d2a612a058261298d565b818152856020838501011115612d3f57600080fd5b612d50826020830160208601612b1f565b95945050505050565b60008251612d6b818460208701612b1f565b9190910192915050565b600060208284031215612d8757600080fd5b5051919050565b600060408284031215612da057600080fd5b82601f830112612daf57600080fd5b612db7612791565b806040840185811115612dc957600080fd5b845b818110156128be578051845260209384019301612dcb565b634e487b7160e01b600052601260045260246000fd5b818103818111156103b2576103b2612c6856fe30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47a2646970667358221220046fd04cf652b14b5d4629e14b0659a560d128e873764c278e0250d47ac47cde64736f6c63430008110033\",\n \"linkReferences\": {},\n \"deployedLinkReferences\": {}\n}\n" +var BN256G2Artifact string = "{\n \"_format\": \"hh-sol-artifact-1\",\n \"contractName\": \"BN256G2\",\n \"sourceName\": \"contracts/common/BN256G2.sol\",\n \"abi\": [\n {\n \"inputs\": [],\n \"name\": \"G2_NEG_X_IM\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"G2_NEG_X_RE\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"G2_NEG_Y_IM\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"G2_NEG_Y_RE\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"pt1xx\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"pt1xy\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"pt1yx\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"pt1yy\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"pt2xx\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"pt2xy\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"pt2yx\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"pt2yy\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"ecTwistAdd\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"s\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"pt1xx\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"pt1xy\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"pt1yx\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"pt1yy\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"ecTwistMul\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"getFieldModulus\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"pure\",\n \"type\": \"function\"\n }\n ],\n \"bytecode\": \"0x608060405234801561001057600080fd5b50610e0d806100206000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c80639b0c399a1161005b5780639b0c399a146100f7578063ad50f9c11461011e578063cbe96a5014610145578063defbcdee1461017857600080fd5b80635120675214610082578063779d890d146100bc578063783bde80146100d0575b600080fd5b6100a97f1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d81565b6040519081526020015b60405180910390f35b600080516020610db88339815191526100a9565b6100a97f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c281565b6100a97f1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed81565b6100a97f275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec81565b610158610153366004610c9c565b61018b565b6040805194855260208501939093529183015260608201526080016100b3565b610158610186366004610cf1565b61032a565b60008080808b15801561019c57508a155b80156101a6575089155b80156101b0575088155b1561021a57871580156101c1575086155b80156101cb575085155b80156101d5575084155b61020a576101e5888888886103c1565b61020a5760405162461bcd60e51b815260040161020190610d2c565b60405180910390fd5b508692508591508490508361031b565b87158015610226575086155b8015610230575085155b801561023a575084155b156102775761024b8c8c8c8c6103c1565b6102675760405162461bcd60e51b815260040161020190610d2c565b508a92508991508890508761031b565b6102838c8c8c8c6103c1565b61029f5760405162461bcd60e51b815260040161020190610d2c565b6102ab888888886103c1565b6102c75760405162461bcd60e51b815260040161020190610d2c565b60006102e18d8d8d8d600160008f8f8f8f60016000610476565b90506103118160005b602090810291909101519083015160408401516060850151608086015160a0870151610701565b9450945094509450505b98509850985098945050505050565b600080808060018815801561033d575087155b8015610347575086155b8015610351575085155b15610365575060019750879550600061038d565b610371898989896103c1565b61038d5760405162461bcd60e51b815260040161020190610d2c565b600061039f8b8b8b8b8b87600061076c565b90506103ac8160006102ea565b929e919d509b50909950975050505050505050565b60008060008060006103d5878789896107ef565b90945092506103e6898981816107ef565b90925090506103f782828b8b6107ef565b909250905061040884848484610860565b909450925061045884847f2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e57e9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2610860565b909450925083158015610469575082155b9998505050505050505050565b61047e610c60565b8815801561048a575087155b156104cc578686868686868660005b60a089019290925260808801929092526060870192909252604086019290925260208581019390935290910201526106f1565b821580156104d8575081155b156104eb578c8c8c8c8c8c866000610499565b6104f785858b8b6107ef565b90955093506105088b8b85856107ef565b6060830152604082015261051e87878b8b6107ef565b909750955061052f8d8d85856107ef565b60a0830152608082018190528714801561054c575060a081015186145b15610591576040810151851480156105675750606081015184145b156105825761057a8d8d8d8d8d8d6108a2565b866000610499565b60016000818180808681610499565b61059d898985856107ef565b90935091506105bd858583600260200201518460035b6020020151610860565b909d509b506105d887878360045b60200201518460056105b3565b909b5099506105e98b8b81816107ef565b909950975061060a89898360045b60200201518460055b60200201516107ef565b909550935061061b89898d8d6107ef565b909950975061062c898985856107ef565b60a083015260808201526106428d8d81816107ef565b9097509550610653878785856107ef565b909750955061066487878b8b610860565b909750955061067585856002610aea565b909350915061068687878585610860565b90975095506106978b8b89896107ef565b602083015281526106aa85858989610860565b909b5099506106bb8d8d8d8d6107ef565b909b5099506106d589898360026020020151846003610600565b909d509b506106e68b8b8f8f610860565b606083015260408201525b9c9b505050505050505050505050565b600080600080600080610712610c7e565b61071c8989610b1d565b909350915061072d8d8d85856107ef565b602083015281526107408b8b85856107ef565b60608301819052604083018290528251602090930151929f929e50909c509a5098505050505050505050565b610774610c60565b87156107e45760018816156107b5578051602082015160408301516060840151608085015160a08601516107b29594939291908d8d8d8d8d8d610476565b90505b6107c38787878787876108a2565b949b509299509097509550935091506107dd600289610d6e565b9750610774565b979650505050505050565b60008061082d600080516020610db8833981519152858809600080516020610db8833981519152858809600080516020610db8833981519152610ba8565b600080516020610db883398151915280868809600080516020610db8833981519152868a09089150915094509492505050565b60008061087c8685600080516020610db8833981519152610ba8565b6108958685600080516020610db8833981519152610ba8565b9150915094509492505050565b6000806000806000806108b3610c60565b6108bf8d8d6003610aea565b602083018190528183526108d591908f8f6107ef565b602083015281526108e88b8b8b8b6107ef565b90995097506108f98d8d8d8d6107ef565b606083015260408201819052610919908260035b60200201518b8b6107ef565b60608301526040820152805161093c908260015b60200201518351846001610600565b6040830151919e509c5061095a908260035b60200201516008610aea565b60a083015260808201526109718d8d8360046105cb565b909d509b50610982898981816107ef565b60a08301526080820152604081015160608201516109a291906004610aea565b60608301819052604083018290526109bc91908f8f610860565b6060830152604082018190526109d49082600361092d565b606083015260408201526109ea8b8b6008610aea565b60208301819052818352610a0091908d8d6107ef565b60208301819052818352610a1791908360046105f7565b602083015280825260408201516060830151610a35928460016105b3565b60608301526040820152610a4b8d8d6002610aea565b6020830152808252610a5f9082600161090d565b60208301528152610a7389898360046105f7565b60a083015260808201819052610a8b9082600561094e565b826004602002018360056020020191909152528060006020020151816001602002015182600260200201518360036020020151846004602002015185600560200201519650965096509650965096505096509650965096509650969050565b600080600080516020610db8833981519152838609600080516020610db883398151915284860991509150935093915050565b60008080610b5e600080516020610db883398151915280878809600080516020610db883398151915287880908600080516020610db8833981519152610bcc565b9050600080516020610db8833981519152818609600080516020610db8833981519152828609610b9c90600080516020610db8833981519152610d90565b92509250509250929050565b60008180610bb857610bb8610d58565b610bc28484610d90565b8508949350505050565b60008060405160208152602080820152602060408201528460608201526002840360808201528360a082015260208160c08360056107d05a03fa90519250905080610c595760405162461bcd60e51b815260206004820152601a60248201527f6572726f722077697468206d6f64756c617220696e76657273650000000000006044820152606401610201565b5092915050565b6040518060c001604052806006906020820280368337509192915050565b60405180608001604052806004906020820280368337509192915050565b600080600080600080600080610100898b031215610cb957600080fd5b505086359860208801359850604088013597606081013597506080810135965060a0810135955060c0810135945060e0013592509050565b600080600080600060a08688031215610d0957600080fd5b505083359560208501359550604085013594606081013594506080013592509050565b602080825260129082015271706f696e74206e6f7420696e20637572766560701b604082015260600190565b634e487b7160e01b600052601260045260246000fd5b600082610d8b57634e487b7160e01b600052601260045260246000fd5b500490565b81810381811115610db157634e487b7160e01b600052601160045260246000fd5b9291505056fe30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47a2646970667358221220e11cec547814ba5f6de4709d5d9d4cc333d30a8791d28c5ad702058c9ff9177c64736f6c63430008110033\",\n \"deployedBytecode\": \"0x608060405234801561001057600080fd5b506004361061007d5760003560e01c80639b0c399a1161005b5780639b0c399a146100f7578063ad50f9c11461011e578063cbe96a5014610145578063defbcdee1461017857600080fd5b80635120675214610082578063779d890d146100bc578063783bde80146100d0575b600080fd5b6100a97f1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d81565b6040519081526020015b60405180910390f35b600080516020610db88339815191526100a9565b6100a97f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c281565b6100a97f1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed81565b6100a97f275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec81565b610158610153366004610c9c565b61018b565b6040805194855260208501939093529183015260608201526080016100b3565b610158610186366004610cf1565b61032a565b60008080808b15801561019c57508a155b80156101a6575089155b80156101b0575088155b1561021a57871580156101c1575086155b80156101cb575085155b80156101d5575084155b61020a576101e5888888886103c1565b61020a5760405162461bcd60e51b815260040161020190610d2c565b60405180910390fd5b508692508591508490508361031b565b87158015610226575086155b8015610230575085155b801561023a575084155b156102775761024b8c8c8c8c6103c1565b6102675760405162461bcd60e51b815260040161020190610d2c565b508a92508991508890508761031b565b6102838c8c8c8c6103c1565b61029f5760405162461bcd60e51b815260040161020190610d2c565b6102ab888888886103c1565b6102c75760405162461bcd60e51b815260040161020190610d2c565b60006102e18d8d8d8d600160008f8f8f8f60016000610476565b90506103118160005b602090810291909101519083015160408401516060850151608086015160a0870151610701565b9450945094509450505b98509850985098945050505050565b600080808060018815801561033d575087155b8015610347575086155b8015610351575085155b15610365575060019750879550600061038d565b610371898989896103c1565b61038d5760405162461bcd60e51b815260040161020190610d2c565b600061039f8b8b8b8b8b87600061076c565b90506103ac8160006102ea565b929e919d509b50909950975050505050505050565b60008060008060006103d5878789896107ef565b90945092506103e6898981816107ef565b90925090506103f782828b8b6107ef565b909250905061040884848484610860565b909450925061045884847f2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e57e9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2610860565b909450925083158015610469575082155b9998505050505050505050565b61047e610c60565b8815801561048a575087155b156104cc578686868686868660005b60a089019290925260808801929092526060870192909252604086019290925260208581019390935290910201526106f1565b821580156104d8575081155b156104eb578c8c8c8c8c8c866000610499565b6104f785858b8b6107ef565b90955093506105088b8b85856107ef565b6060830152604082015261051e87878b8b6107ef565b909750955061052f8d8d85856107ef565b60a0830152608082018190528714801561054c575060a081015186145b15610591576040810151851480156105675750606081015184145b156105825761057a8d8d8d8d8d8d6108a2565b866000610499565b60016000818180808681610499565b61059d898985856107ef565b90935091506105bd858583600260200201518460035b6020020151610860565b909d509b506105d887878360045b60200201518460056105b3565b909b5099506105e98b8b81816107ef565b909950975061060a89898360045b60200201518460055b60200201516107ef565b909550935061061b89898d8d6107ef565b909950975061062c898985856107ef565b60a083015260808201526106428d8d81816107ef565b9097509550610653878785856107ef565b909750955061066487878b8b610860565b909750955061067585856002610aea565b909350915061068687878585610860565b90975095506106978b8b89896107ef565b602083015281526106aa85858989610860565b909b5099506106bb8d8d8d8d6107ef565b909b5099506106d589898360026020020151846003610600565b909d509b506106e68b8b8f8f610860565b606083015260408201525b9c9b505050505050505050505050565b600080600080600080610712610c7e565b61071c8989610b1d565b909350915061072d8d8d85856107ef565b602083015281526107408b8b85856107ef565b60608301819052604083018290528251602090930151929f929e50909c509a5098505050505050505050565b610774610c60565b87156107e45760018816156107b5578051602082015160408301516060840151608085015160a08601516107b29594939291908d8d8d8d8d8d610476565b90505b6107c38787878787876108a2565b949b509299509097509550935091506107dd600289610d6e565b9750610774565b979650505050505050565b60008061082d600080516020610db8833981519152858809600080516020610db8833981519152858809600080516020610db8833981519152610ba8565b600080516020610db883398151915280868809600080516020610db8833981519152868a09089150915094509492505050565b60008061087c8685600080516020610db8833981519152610ba8565b6108958685600080516020610db8833981519152610ba8565b9150915094509492505050565b6000806000806000806108b3610c60565b6108bf8d8d6003610aea565b602083018190528183526108d591908f8f6107ef565b602083015281526108e88b8b8b8b6107ef565b90995097506108f98d8d8d8d6107ef565b606083015260408201819052610919908260035b60200201518b8b6107ef565b60608301526040820152805161093c908260015b60200201518351846001610600565b6040830151919e509c5061095a908260035b60200201516008610aea565b60a083015260808201526109718d8d8360046105cb565b909d509b50610982898981816107ef565b60a08301526080820152604081015160608201516109a291906004610aea565b60608301819052604083018290526109bc91908f8f610860565b6060830152604082018190526109d49082600361092d565b606083015260408201526109ea8b8b6008610aea565b60208301819052818352610a0091908d8d6107ef565b60208301819052818352610a1791908360046105f7565b602083015280825260408201516060830151610a35928460016105b3565b60608301526040820152610a4b8d8d6002610aea565b6020830152808252610a5f9082600161090d565b60208301528152610a7389898360046105f7565b60a083015260808201819052610a8b9082600561094e565b826004602002018360056020020191909152528060006020020151816001602002015182600260200201518360036020020151846004602002015185600560200201519650965096509650965096505096509650965096509650969050565b600080600080516020610db8833981519152838609600080516020610db883398151915284860991509150935093915050565b60008080610b5e600080516020610db883398151915280878809600080516020610db883398151915287880908600080516020610db8833981519152610bcc565b9050600080516020610db8833981519152818609600080516020610db8833981519152828609610b9c90600080516020610db8833981519152610d90565b92509250509250929050565b60008180610bb857610bb8610d58565b610bc28484610d90565b8508949350505050565b60008060405160208152602080820152602060408201528460608201526002840360808201528360a082015260208160c08360056107d05a03fa90519250905080610c595760405162461bcd60e51b815260206004820152601a60248201527f6572726f722077697468206d6f64756c617220696e76657273650000000000006044820152606401610201565b5092915050565b6040518060c001604052806006906020820280368337509192915050565b60405180608001604052806004906020820280368337509192915050565b600080600080600080600080610100898b031215610cb957600080fd5b505086359860208801359850604088013597606081013597506080810135965060a0810135955060c0810135945060e0013592509050565b600080600080600060a08688031215610d0957600080fd5b505083359560208501359550604085013594606081013594506080013592509050565b602080825260129082015271706f696e74206e6f7420696e20637572766560701b604082015260600190565b634e487b7160e01b600052601260045260246000fd5b600082610d8b57634e487b7160e01b600052601260045260246000fd5b500490565b81810381811115610db157634e487b7160e01b600052601160045260246000fd5b9291505056fe30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47a2646970667358221220e11cec547814ba5f6de4709d5d9d4cc333d30a8791d28c5ad702058c9ff9177c64736f6c63430008110033\",\n \"linkReferences\": {},\n \"deployedLinkReferences\": {}\n}\n" +var StateReceiverArtifact string = "{\n \"_format\": \"hh-sol-artifact-1\",\n \"contractName\": \"StateReceiver\",\n \"sourceName\": \"contracts/child/StateReceiver.sol\",\n \"abi\": [\n {\n \"inputs\": [\n {\n \"internalType\": \"string\",\n \"name\": \"only\",\n \"type\": \"string\"\n }\n ],\n \"name\": \"Unauthorized\",\n \"type\": \"error\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"uint256\",\n \"name\": \"startId\",\n \"type\": \"uint256\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"uint256\",\n \"name\": \"endId\",\n \"type\": \"uint256\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"bytes32\",\n \"name\": \"root\",\n \"type\": \"bytes32\"\n }\n ],\n \"name\": \"NewCommitment\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"uint256\",\n \"name\": \"counter\",\n \"type\": \"uint256\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"bool\",\n \"name\": \"status\",\n \"type\": \"bool\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"bytes\",\n \"name\": \"message\",\n \"type\": \"bytes\"\n }\n ],\n \"name\": \"StateSyncResult\",\n \"type\": \"event\"\n },\n {\n \"inputs\": [],\n \"name\": \"NATIVE_TOKEN_CONTRACT\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"NATIVE_TRANSFER_PRECOMPILE\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"NATIVE_TRANSFER_PRECOMPILE_GAS\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"SYSTEM\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"VALIDATOR_PKCHECK_PRECOMPILE\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"VALIDATOR_PKCHECK_PRECOMPILE_GAS\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"bytes32[][]\",\n \"name\": \"proofs\",\n \"type\": \"bytes32[][]\"\n },\n {\n \"components\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"id\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"address\",\n \"name\": \"sender\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"address\",\n \"name\": \"receiver\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"bytes\",\n \"name\": \"data\",\n \"type\": \"bytes\"\n }\n ],\n \"internalType\": \"struct StateReceiver.StateSync[]\",\n \"name\": \"objs\",\n \"type\": \"tuple[]\"\n }\n ],\n \"name\": \"batchExecute\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"components\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"startId\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"endId\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"bytes32\",\n \"name\": \"root\",\n \"type\": \"bytes32\"\n }\n ],\n \"internalType\": \"struct StateReceiver.StateSyncCommitment\",\n \"name\": \"commitment\",\n \"type\": \"tuple\"\n },\n {\n \"internalType\": \"bytes\",\n \"name\": \"signature\",\n \"type\": \"bytes\"\n },\n {\n \"internalType\": \"bytes\",\n \"name\": \"bitmap\",\n \"type\": \"bytes\"\n }\n ],\n \"name\": \"commit\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"commitmentCounter\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"commitmentIds\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"commitments\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"startId\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"endId\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"bytes32\",\n \"name\": \"root\",\n \"type\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"bytes32[]\",\n \"name\": \"proof\",\n \"type\": \"bytes32[]\"\n },\n {\n \"components\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"id\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"address\",\n \"name\": \"sender\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"address\",\n \"name\": \"receiver\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"bytes\",\n \"name\": \"data\",\n \"type\": \"bytes\"\n }\n ],\n \"internalType\": \"struct StateReceiver.StateSync\",\n \"name\": \"obj\",\n \"type\": \"tuple\"\n }\n ],\n \"name\": \"execute\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"id\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"getCommitmentByStateSyncId\",\n \"outputs\": [\n {\n \"components\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"startId\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"endId\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"bytes32\",\n \"name\": \"root\",\n \"type\": \"bytes32\"\n }\n ],\n \"internalType\": \"struct StateReceiver.StateSyncCommitment\",\n \"name\": \"\",\n \"type\": \"tuple\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"id\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"getRootByStateSyncId\",\n \"outputs\": [\n {\n \"internalType\": \"bytes32\",\n \"name\": \"\",\n \"type\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"lastCommittedId\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"processedStateSyncs\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n }\n ],\n \"bytecode\": \"0x608060405234801561001057600080fd5b5061130f806100206000396000f3fe608060405234801561001057600080fd5b50600436106101005760003560e01c80639017c12711610097578063c59a18f711610066578063c59a18f71461022d578063c6df461714610240578063e0563ab114610253578063eb70ef441461025c57600080fd5b80639017c127146101fe578063947287cf1461021157806397e5230d1461021a578063ad240c2a1461022457600080fd5b806349ce8997116100d357806349ce89971461015e57806350d5b95b146101a857806351351d53146101bd578063544c5e0f146101cb57600080fd5b8063196f1b2d1461010557806323e281cf1461012b578063284017f5146101345780633b878c2214610155575b600080fd5b610118610113366004610c8d565b610291565b6040519081526020015b60405180910390f35b61011860325481565b61013d61202081565b6040516001600160a01b039091168152602001610122565b61013d61101081565b61018d61016c366004610c8d565b60356020526000908152604090208054600182015460029092015490919083565b60408051938452602084019290925290820152606001610122565b6101bb6101b6366004610cf2565b610310565b005b61013d6002600160a01b0381565b6101ee6101d9366004610c8d565b60346020526000908152604090205460ff1681565b6040519015158152602001610122565b6101bb61020c366004610d62565b6103ba565b61011861520881565b610118620249f081565b61011860335481565b61011861023b366004610c8d565b6104dc565b6101bb61024e366004610e10565b6104fd565b61013d61203081565b61026f61026a366004610c8d565b6106e1565b6040805182518152602080840151908201529181015190820152606001610122565b6000806035816102a26036866107a4565b815260208101919091526040016000206002015490508061030a5760405162461bcd60e51b815260206004820152601d60248201527f537461746552656365697665723a204e4f5f524f4f545f464f525f494400000060448201526064015b60405180910390fd5b92915050565b600061031c82356106e1565b805190915061036f90610330908435610eaf565b82604001518686866040516020016103489190610f07565b6040516020818303038152906040528051906020012061085190949392919063ffffffff16565b6103ab5760405162461bcd60e51b815260206004820152600d60248201526c24a72b20a624a22fa82927a7a360991b6044820152606401610301565b6103b48261096a565b50505050565b8260005b818110156104d45760006103f58585848181106103dd576103dd610fa4565b90506020028101906103ef9190610fba565b356106e1565b9050600061048d826000015187878681811061041357610413610fa4565b90506020028101906104259190610fba565b610430919035610eaf565b83604001518a8a8781811061044757610447610fa4565b90506020028101906104599190610fda565b8a8a8981811061046b5761046b610fa4565b905060200281019061047d9190610fba565b6040516020016103489190610f07565b90508061049e5750506001016103be565b6104ca8686858181106104b3576104b3610fa4565b90506020028101906104c59190610fba565b61096a565b50506001016103be565b505050505050565b603681815481106104ec57600080fd5b600091825260209091200154905081565b336002600160a01b03146105415760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b6044820152606401610301565b60335461054f906001611024565b8535146105915760405162461bcd60e51b815260206004820152601060248201526f1253959053125117d4d510549517d25160821b6044820152606401610301565b8435602086013510156105d75760405162461bcd60e51b815260206004820152600e60248201526d1253959053125117d1539117d25160921b6044820152606401610301565b6040805186356020808301919091528701358183015290860135606082015261061c906080016040516020818303038152906040528051906020012085858585610b67565b6032805486916035916000918261063283611037565b90915550815260208082019290925260409081016000208335815591830135600183015582013560028201555050603680546001810182556000919091526020868101357f4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b89092018290556033829055604080519088013581528735917f11efd893530b26afc66d488ff54cb15df117cb6e0e4a08c6dcb166d766c3bf3b910160405180910390a35050505050565b60408051606081018252600080825260208201819052918101829052906107096036846107a4565b60365490915081036107695760405162461bcd60e51b815260206004820152602360248201527f537461746552656365697665723a204e4f5f434f4d4d49544d454e545f464f5260448201526217d25160ea1b6064820152608401610301565b600090815260356020908152604091829020825160608101845281548152600182015492810192909252600201549181019190915292915050565b815460009081036107b75750600061030a565b82546000905b808210156108045760006107d18383610c6b565b600087815260209020909150859082015411156107f0578091506107fe565b6107fb816001611024565b92505b506107bd565b60008211801561083057508361082d8661081f600186610eaf565b600091825260209091200190565b54145b1561084957610840600183610eaf565b9250505061030a565b509392505050565b60008161085f816002611134565b86106108a25760405162461bcd60e51b81526020600482015260126024820152710929cac82989288be988a828cbe929c888ab60731b6044820152606401610301565b8660005b8281101561095c5760008686838181106108c2576108c2610fa4565b9050602002013590506002896108d89190611156565b60000361091057604080516020810185905290810182905260600160405160208183030381529060405280519060200120925061093d565b60408051602081018390529081018490526060016040516020818303038152906040528051906020012092505b61094860028a61116a565b9850508061095590611037565b90506108a6565b509094149695505050505050565b803560009081526034602052604090205460ff16156109da5760405162461bcd60e51b815260206004820152602660248201527f537461746552656365697665723a2053544154455f53594e435f49535f50524f60448201526510d154d4d15160d21b6064820152608401610301565b6109ea606082016040830161117e565b6001600160a01b03163b600003610a3e576040805160208082526000908201819052918335917f31c652130602f3ce96ceaf8a4c2b8b49f049166c6fcf2eb31943a75ec7c936ae910160405180910390a350565b8035600090815260346020526040808220805460ff191660011790558190610a6c906060850190850161117e565b6001600160a01b03168335610a87604086016020870161117e565b610a946060870187611199565b604051602401610aa794939291906111e0565b60408051601f198184030181529181526020820180516001600160e01b031663eeb4994560e01b17905251610adc9190611239565b6000604051808303816000865af19150503d8060008114610b19576040519150601f19603f3d011682016040523d82523d6000602084013e610b1e565b606091505b509150915081151583600001357f31c652130602f3ce96ceaf8a4c2b8b49f049166c6fcf2eb31943a75ec7c936ae83604051610b5a919061124b565b60405180910390a3505050565b6000806120306001600160a01b0316620249f08888888888604051602001610b9395949392919061127e565b60408051601f1981840301815290829052610bad91611239565b6000604051808303818686fa925050503d8060008114610be9576040519150601f19603f3d011682016040523d82523d6000602084013e610bee565b606091505b5091509150600081806020019051810190610c0991906112b7565b9050828015610c155750805b610c615760405162461bcd60e51b815260206004820152601d60248201527f5349474e41545552455f564552494649434154494f4e5f4641494c45440000006044820152606401610301565b5050505050505050565b6000610c7a600284841861116a565b610c8690848416611024565b9392505050565b600060208284031215610c9f57600080fd5b5035919050565b60008083601f840112610cb857600080fd5b50813567ffffffffffffffff811115610cd057600080fd5b6020830191508360208260051b8501011115610ceb57600080fd5b9250929050565b600080600060408486031215610d0757600080fd5b833567ffffffffffffffff80821115610d1f57600080fd5b610d2b87838801610ca6565b90955093506020860135915080821115610d4457600080fd5b50840160808187031215610d5757600080fd5b809150509250925092565b60008060008060408587031215610d7857600080fd5b843567ffffffffffffffff80821115610d9057600080fd5b610d9c88838901610ca6565b90965094506020870135915080821115610db557600080fd5b50610dc287828801610ca6565b95989497509550505050565b60008083601f840112610de057600080fd5b50813567ffffffffffffffff811115610df857600080fd5b602083019150836020828501011115610ceb57600080fd5b600080600080600085870360a0811215610e2957600080fd5b6060811215610e3757600080fd5b50859450606086013567ffffffffffffffff80821115610e5657600080fd5b610e6289838a01610dce565b90965094506080880135915080821115610e7b57600080fd5b50610e8888828901610dce565b969995985093965092949392505050565b634e487b7160e01b600052601160045260246000fd5b8181038181111561030a5761030a610e99565b80356001600160a01b0381168114610ed957600080fd5b919050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b60208152813560208201526000610f2060208401610ec2565b60018060a01b03808216604085015280610f3c60408701610ec2565b16606085015250506060830135601e19843603018112610f5b57600080fd5b830160208101903567ffffffffffffffff811115610f7857600080fd5b803603821315610f8757600080fd5b608080850152610f9b60a085018284610ede565b95945050505050565b634e487b7160e01b600052603260045260246000fd5b60008235607e19833603018112610fd057600080fd5b9190910192915050565b6000808335601e19843603018112610ff157600080fd5b83018035915067ffffffffffffffff82111561100c57600080fd5b6020019150600581901b3603821315610ceb57600080fd5b8082018082111561030a5761030a610e99565b60006001820161104957611049610e99565b5060010190565b600181815b8085111561108b57816000190482111561107157611071610e99565b8085161561107e57918102915b93841c9390800290611055565b509250929050565b6000826110a25750600161030a565b816110af5750600061030a565b81600181146110c557600281146110cf576110eb565b600191505061030a565b60ff8411156110e0576110e0610e99565b50506001821b61030a565b5060208310610133831016604e8410600b841016171561110e575081810a61030a565b6111188383611050565b806000190482111561112c5761112c610e99565b029392505050565b6000610c868383611093565b634e487b7160e01b600052601260045260246000fd5b60008261116557611165611140565b500690565b60008261117957611179611140565b500490565b60006020828403121561119057600080fd5b610c8682610ec2565b6000808335601e198436030181126111b057600080fd5b83018035915067ffffffffffffffff8211156111cb57600080fd5b602001915036819003821315610ceb57600080fd5b8481526001600160a01b038416602082015260606040820181905260009061120b9083018486610ede565b9695505050505050565b60005b83811015611230578181015183820152602001611218565b50506000910152565b60008251610fd0818460208701611215565b602081526000825180602084015261126a816040850160208701611215565b601f01601f19169190910160400192915050565b858152606060208201526000611298606083018688610ede565b82810360408401526112ab818587610ede565b98975050505050505050565b6000602082840312156112c957600080fd5b81518015158114610c8657600080fdfea26469706673582212200b2288fa1613c6744dc71c09ab12dcdb5dd3351802c023c03f7ebfe305608e6264736f6c63430008110033\",\n \"deployedBytecode\": \"0x608060405234801561001057600080fd5b50600436106101005760003560e01c80639017c12711610097578063c59a18f711610066578063c59a18f71461022d578063c6df461714610240578063e0563ab114610253578063eb70ef441461025c57600080fd5b80639017c127146101fe578063947287cf1461021157806397e5230d1461021a578063ad240c2a1461022457600080fd5b806349ce8997116100d357806349ce89971461015e57806350d5b95b146101a857806351351d53146101bd578063544c5e0f146101cb57600080fd5b8063196f1b2d1461010557806323e281cf1461012b578063284017f5146101345780633b878c2214610155575b600080fd5b610118610113366004610c8d565b610291565b6040519081526020015b60405180910390f35b61011860325481565b61013d61202081565b6040516001600160a01b039091168152602001610122565b61013d61101081565b61018d61016c366004610c8d565b60356020526000908152604090208054600182015460029092015490919083565b60408051938452602084019290925290820152606001610122565b6101bb6101b6366004610cf2565b610310565b005b61013d6002600160a01b0381565b6101ee6101d9366004610c8d565b60346020526000908152604090205460ff1681565b6040519015158152602001610122565b6101bb61020c366004610d62565b6103ba565b61011861520881565b610118620249f081565b61011860335481565b61011861023b366004610c8d565b6104dc565b6101bb61024e366004610e10565b6104fd565b61013d61203081565b61026f61026a366004610c8d565b6106e1565b6040805182518152602080840151908201529181015190820152606001610122565b6000806035816102a26036866107a4565b815260208101919091526040016000206002015490508061030a5760405162461bcd60e51b815260206004820152601d60248201527f537461746552656365697665723a204e4f5f524f4f545f464f525f494400000060448201526064015b60405180910390fd5b92915050565b600061031c82356106e1565b805190915061036f90610330908435610eaf565b82604001518686866040516020016103489190610f07565b6040516020818303038152906040528051906020012061085190949392919063ffffffff16565b6103ab5760405162461bcd60e51b815260206004820152600d60248201526c24a72b20a624a22fa82927a7a360991b6044820152606401610301565b6103b48261096a565b50505050565b8260005b818110156104d45760006103f58585848181106103dd576103dd610fa4565b90506020028101906103ef9190610fba565b356106e1565b9050600061048d826000015187878681811061041357610413610fa4565b90506020028101906104259190610fba565b610430919035610eaf565b83604001518a8a8781811061044757610447610fa4565b90506020028101906104599190610fda565b8a8a8981811061046b5761046b610fa4565b905060200281019061047d9190610fba565b6040516020016103489190610f07565b90508061049e5750506001016103be565b6104ca8686858181106104b3576104b3610fa4565b90506020028101906104c59190610fba565b61096a565b50506001016103be565b505050505050565b603681815481106104ec57600080fd5b600091825260209091200154905081565b336002600160a01b03146105415760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b6044820152606401610301565b60335461054f906001611024565b8535146105915760405162461bcd60e51b815260206004820152601060248201526f1253959053125117d4d510549517d25160821b6044820152606401610301565b8435602086013510156105d75760405162461bcd60e51b815260206004820152600e60248201526d1253959053125117d1539117d25160921b6044820152606401610301565b6040805186356020808301919091528701358183015290860135606082015261061c906080016040516020818303038152906040528051906020012085858585610b67565b6032805486916035916000918261063283611037565b90915550815260208082019290925260409081016000208335815591830135600183015582013560028201555050603680546001810182556000919091526020868101357f4a11f94e20a93c79f6ec743a1954ec4fc2c08429ae2122118bf234b2185c81b89092018290556033829055604080519088013581528735917f11efd893530b26afc66d488ff54cb15df117cb6e0e4a08c6dcb166d766c3bf3b910160405180910390a35050505050565b60408051606081018252600080825260208201819052918101829052906107096036846107a4565b60365490915081036107695760405162461bcd60e51b815260206004820152602360248201527f537461746552656365697665723a204e4f5f434f4d4d49544d454e545f464f5260448201526217d25160ea1b6064820152608401610301565b600090815260356020908152604091829020825160608101845281548152600182015492810192909252600201549181019190915292915050565b815460009081036107b75750600061030a565b82546000905b808210156108045760006107d18383610c6b565b600087815260209020909150859082015411156107f0578091506107fe565b6107fb816001611024565b92505b506107bd565b60008211801561083057508361082d8661081f600186610eaf565b600091825260209091200190565b54145b1561084957610840600183610eaf565b9250505061030a565b509392505050565b60008161085f816002611134565b86106108a25760405162461bcd60e51b81526020600482015260126024820152710929cac82989288be988a828cbe929c888ab60731b6044820152606401610301565b8660005b8281101561095c5760008686838181106108c2576108c2610fa4565b9050602002013590506002896108d89190611156565b60000361091057604080516020810185905290810182905260600160405160208183030381529060405280519060200120925061093d565b60408051602081018390529081018490526060016040516020818303038152906040528051906020012092505b61094860028a61116a565b9850508061095590611037565b90506108a6565b509094149695505050505050565b803560009081526034602052604090205460ff16156109da5760405162461bcd60e51b815260206004820152602660248201527f537461746552656365697665723a2053544154455f53594e435f49535f50524f60448201526510d154d4d15160d21b6064820152608401610301565b6109ea606082016040830161117e565b6001600160a01b03163b600003610a3e576040805160208082526000908201819052918335917f31c652130602f3ce96ceaf8a4c2b8b49f049166c6fcf2eb31943a75ec7c936ae910160405180910390a350565b8035600090815260346020526040808220805460ff191660011790558190610a6c906060850190850161117e565b6001600160a01b03168335610a87604086016020870161117e565b610a946060870187611199565b604051602401610aa794939291906111e0565b60408051601f198184030181529181526020820180516001600160e01b031663eeb4994560e01b17905251610adc9190611239565b6000604051808303816000865af19150503d8060008114610b19576040519150601f19603f3d011682016040523d82523d6000602084013e610b1e565b606091505b509150915081151583600001357f31c652130602f3ce96ceaf8a4c2b8b49f049166c6fcf2eb31943a75ec7c936ae83604051610b5a919061124b565b60405180910390a3505050565b6000806120306001600160a01b0316620249f08888888888604051602001610b9395949392919061127e565b60408051601f1981840301815290829052610bad91611239565b6000604051808303818686fa925050503d8060008114610be9576040519150601f19603f3d011682016040523d82523d6000602084013e610bee565b606091505b5091509150600081806020019051810190610c0991906112b7565b9050828015610c155750805b610c615760405162461bcd60e51b815260206004820152601d60248201527f5349474e41545552455f564552494649434154494f4e5f4641494c45440000006044820152606401610301565b5050505050505050565b6000610c7a600284841861116a565b610c8690848416611024565b9392505050565b600060208284031215610c9f57600080fd5b5035919050565b60008083601f840112610cb857600080fd5b50813567ffffffffffffffff811115610cd057600080fd5b6020830191508360208260051b8501011115610ceb57600080fd5b9250929050565b600080600060408486031215610d0757600080fd5b833567ffffffffffffffff80821115610d1f57600080fd5b610d2b87838801610ca6565b90955093506020860135915080821115610d4457600080fd5b50840160808187031215610d5757600080fd5b809150509250925092565b60008060008060408587031215610d7857600080fd5b843567ffffffffffffffff80821115610d9057600080fd5b610d9c88838901610ca6565b90965094506020870135915080821115610db557600080fd5b50610dc287828801610ca6565b95989497509550505050565b60008083601f840112610de057600080fd5b50813567ffffffffffffffff811115610df857600080fd5b602083019150836020828501011115610ceb57600080fd5b600080600080600085870360a0811215610e2957600080fd5b6060811215610e3757600080fd5b50859450606086013567ffffffffffffffff80821115610e5657600080fd5b610e6289838a01610dce565b90965094506080880135915080821115610e7b57600080fd5b50610e8888828901610dce565b969995985093965092949392505050565b634e487b7160e01b600052601160045260246000fd5b8181038181111561030a5761030a610e99565b80356001600160a01b0381168114610ed957600080fd5b919050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b60208152813560208201526000610f2060208401610ec2565b60018060a01b03808216604085015280610f3c60408701610ec2565b16606085015250506060830135601e19843603018112610f5b57600080fd5b830160208101903567ffffffffffffffff811115610f7857600080fd5b803603821315610f8757600080fd5b608080850152610f9b60a085018284610ede565b95945050505050565b634e487b7160e01b600052603260045260246000fd5b60008235607e19833603018112610fd057600080fd5b9190910192915050565b6000808335601e19843603018112610ff157600080fd5b83018035915067ffffffffffffffff82111561100c57600080fd5b6020019150600581901b3603821315610ceb57600080fd5b8082018082111561030a5761030a610e99565b60006001820161104957611049610e99565b5060010190565b600181815b8085111561108b57816000190482111561107157611071610e99565b8085161561107e57918102915b93841c9390800290611055565b509250929050565b6000826110a25750600161030a565b816110af5750600061030a565b81600181146110c557600281146110cf576110eb565b600191505061030a565b60ff8411156110e0576110e0610e99565b50506001821b61030a565b5060208310610133831016604e8410600b841016171561110e575081810a61030a565b6111188383611050565b806000190482111561112c5761112c610e99565b029392505050565b6000610c868383611093565b634e487b7160e01b600052601260045260246000fd5b60008261116557611165611140565b500690565b60008261117957611179611140565b500490565b60006020828403121561119057600080fd5b610c8682610ec2565b6000808335601e198436030181126111b057600080fd5b83018035915067ffffffffffffffff8211156111cb57600080fd5b602001915036819003821315610ceb57600080fd5b8481526001600160a01b038416602082015260606040820181905260009061120b9083018486610ede565b9695505050505050565b60005b83811015611230578181015183820152602001611218565b50506000910152565b60008251610fd0818460208701611215565b602081526000825180602084015261126a816040850160208701611215565b601f01601f19169190910160400192915050565b858152606060208201526000611298606083018688610ede565b82810360408401526112ab818587610ede565b98975050505050505050565b6000602082840312156112c957600080fd5b81518015158114610c8657600080fdfea26469706673582212200b2288fa1613c6744dc71c09ab12dcdb5dd3351802c023c03f7ebfe305608e6264736f6c63430008110033\",\n \"linkReferences\": {},\n \"deployedLinkReferences\": {}\n}\n" +var StateSenderArtifact string = "{\n \"_format\": \"hh-sol-artifact-1\",\n \"contractName\": \"StateSender\",\n \"sourceName\": \"contracts/root/StateSender.sol\",\n \"abi\": [\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"uint256\",\n \"name\": \"id\",\n \"type\": \"uint256\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"sender\",\n \"type\": \"address\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"receiver\",\n \"type\": \"address\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"bytes\",\n \"name\": \"data\",\n \"type\": \"bytes\"\n }\n ],\n \"name\": \"StateSynced\",\n \"type\": \"event\"\n },\n {\n \"inputs\": [],\n \"name\": \"MAX_LENGTH\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"counter\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"receiver\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"bytes\",\n \"name\": \"data\",\n \"type\": \"bytes\"\n }\n ],\n \"name\": \"syncState\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n }\n ],\n \"bytecode\": \"0x608060405234801561001057600080fd5b50610297806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806316f198311461004657806361bc221a1461005b578063a6f9885c14610076575b600080fd5b61005961005436600461017a565b61007f565b005b61006460005481565b60405190815260200160405180910390f35b61006461080081565b6001600160a01b0383166100cd5760405162461bcd60e51b815260206004820152601060248201526f24a72b20a624a22fa922a1a2a4ab22a960811b60448201526064015b60405180910390fd5b6108008111156101145760405162461bcd60e51b815260206004820152601260248201527108ab0868a8a88a6be9a82b0be988a9c8ea8960731b60448201526064016100c4565b826001600160a01b0316336001600160a01b031660008081546101369061020b565b9190508190557fd1d7f6609674cc5871fdb4b0bcd4f0a214118411de9e38983866514f22659165858560405161016d929190610232565b60405180910390a4505050565b60008060006040848603121561018f57600080fd5b83356001600160a01b03811681146101a657600080fd5b9250602084013567ffffffffffffffff808211156101c357600080fd5b818601915086601f8301126101d757600080fd5b8135818111156101e657600080fd5b8760208285010111156101f857600080fd5b6020830194508093505050509250925092565b60006001820161022b57634e487b7160e01b600052601160045260246000fd5b5060010190565b60208152816020820152818360408301376000818301604090810191909152601f909201601f1916010191905056fea26469706673582212205a1ba30322d7585ff3ef32240bc1cf527147c769cbeaf0c55fdf5523649a36ae64736f6c63430008110033\",\n \"deployedBytecode\": \"0x608060405234801561001057600080fd5b50600436106100415760003560e01c806316f198311461004657806361bc221a1461005b578063a6f9885c14610076575b600080fd5b61005961005436600461017a565b61007f565b005b61006460005481565b60405190815260200160405180910390f35b61006461080081565b6001600160a01b0383166100cd5760405162461bcd60e51b815260206004820152601060248201526f24a72b20a624a22fa922a1a2a4ab22a960811b60448201526064015b60405180910390fd5b6108008111156101145760405162461bcd60e51b815260206004820152601260248201527108ab0868a8a88a6be9a82b0be988a9c8ea8960731b60448201526064016100c4565b826001600160a01b0316336001600160a01b031660008081546101369061020b565b9190508190557fd1d7f6609674cc5871fdb4b0bcd4f0a214118411de9e38983866514f22659165858560405161016d929190610232565b60405180910390a4505050565b60008060006040848603121561018f57600080fd5b83356001600160a01b03811681146101a657600080fd5b9250602084013567ffffffffffffffff808211156101c357600080fd5b818601915086601f8301126101d757600080fd5b8135818111156101e657600080fd5b8760208285010111156101f857600080fd5b6020830194508093505050509250925092565b60006001820161022b57634e487b7160e01b600052601160045260246000fd5b5060010190565b60208152816020820152818360408301376000818301604090810191909152601f909201601f1916010191905056fea26469706673582212205a1ba30322d7585ff3ef32240bc1cf527147c769cbeaf0c55fdf5523649a36ae64736f6c63430008110033\",\n \"linkReferences\": {},\n \"deployedLinkReferences\": {}\n}\n" +var ChildValidatorSetArtifact string = "{\n \"_format\": \"hh-sol-artifact-1\",\n \"contractName\": \"ChildValidatorSet\",\n \"sourceName\": \"contracts/child/ChildValidatorSet.sol\",\n \"abi\": [\n {\n \"inputs\": [],\n \"name\": \"AmountZero\",\n \"type\": \"error\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"Exists\",\n \"type\": \"error\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"NoTokensDelegated\",\n \"type\": \"error\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"NotFound\",\n \"type\": \"error\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"string\",\n \"name\": \"src\",\n \"type\": \"string\"\n },\n {\n \"internalType\": \"string\",\n \"name\": \"msg\",\n \"type\": \"string\"\n }\n ],\n \"name\": \"StakeRequirement\",\n \"type\": \"error\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"string\",\n \"name\": \"only\",\n \"type\": \"string\"\n }\n ],\n \"name\": \"Unauthorized\",\n \"type\": \"error\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"AddedToWhitelist\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"delegator\",\n \"type\": \"address\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"uint256\",\n \"name\": \"amount\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"Delegated\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"delegator\",\n \"type\": \"address\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"bool\",\n \"name\": \"restake\",\n \"type\": \"bool\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"uint256\",\n \"name\": \"amount\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"DelegatorRewardClaimed\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"uint256\",\n \"name\": \"amount\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"DelegatorRewardDistributed\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"key\",\n \"type\": \"address\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"uint256\",\n \"name\": \"epoch\",\n \"type\": \"uint256\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"uint256\",\n \"name\": \"pbftRound\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"DoubleSignerSlashed\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": false,\n \"internalType\": \"uint8\",\n \"name\": \"version\",\n \"type\": \"uint8\"\n }\n ],\n \"name\": \"Initialized\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"uint256\",\n \"name\": \"id\",\n \"type\": \"uint256\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"uint256\",\n \"name\": \"startBlock\",\n \"type\": \"uint256\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"uint256\",\n \"name\": \"endBlock\",\n \"type\": \"uint256\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"bytes32\",\n \"name\": \"epochRoot\",\n \"type\": \"bytes32\"\n }\n ],\n \"name\": \"NewEpoch\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"uint256[4]\",\n \"name\": \"blsKey\",\n \"type\": \"uint256[4]\"\n }\n ],\n \"name\": \"NewValidator\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"previousOwner\",\n \"type\": \"address\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"newOwner\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"OwnershipTransferStarted\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"previousOwner\",\n \"type\": \"address\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"newOwner\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"OwnershipTransferred\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"RemovedFromWhitelist\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"uint256\",\n \"name\": \"amount\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"Staked\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"delegator\",\n \"type\": \"address\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"uint256\",\n \"name\": \"amount\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"Undelegated\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"uint256\",\n \"name\": \"amount\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"Unstaked\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"uint256\",\n \"name\": \"amount\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"ValidatorRewardClaimed\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"uint256\",\n \"name\": \"amount\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"ValidatorRewardDistributed\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"account\",\n \"type\": \"address\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"to\",\n \"type\": \"address\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"uint256\",\n \"name\": \"amount\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"Withdrawal\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"account\",\n \"type\": \"address\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"uint256\",\n \"name\": \"amount\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"WithdrawalRegistered\",\n \"type\": \"event\"\n },\n {\n \"inputs\": [],\n \"name\": \"ACTIVE_VALIDATOR_SET_SIZE\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"DOUBLE_SIGNING_SLASHING_PERCENT\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"MAX_COMMISSION\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"MAX_VALIDATOR_SET_SIZE\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"NATIVE_TOKEN_CONTRACT\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"NATIVE_TRANSFER_PRECOMPILE\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"NATIVE_TRANSFER_PRECOMPILE_GAS\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"NEW_VALIDATOR_SIG\",\n \"outputs\": [\n {\n \"internalType\": \"bytes32\",\n \"name\": \"\",\n \"type\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"REWARD_PRECISION\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"SYSTEM\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"VALIDATOR_PKCHECK_PRECOMPILE\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"VALIDATOR_PKCHECK_PRECOMPILE_GAS\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"WITHDRAWAL_WAIT_PERIOD\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"acceptOwnership\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address[]\",\n \"name\": \"whitelistAddreses\",\n \"type\": \"address[]\"\n }\n ],\n \"name\": \"addToWhitelist\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"bls\",\n \"outputs\": [\n {\n \"internalType\": \"contract IBLS\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"bool\",\n \"name\": \"restake\",\n \"type\": \"bool\"\n }\n ],\n \"name\": \"claimDelegatorReward\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"claimValidatorReward\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"id\",\n \"type\": \"uint256\"\n },\n {\n \"components\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"startBlock\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"endBlock\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"bytes32\",\n \"name\": \"epochRoot\",\n \"type\": \"bytes32\"\n }\n ],\n \"internalType\": \"struct Epoch\",\n \"name\": \"epoch\",\n \"type\": \"tuple\"\n },\n {\n \"components\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"epochId\",\n \"type\": \"uint256\"\n },\n {\n \"components\": [\n {\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"signedBlocks\",\n \"type\": \"uint256\"\n }\n ],\n \"internalType\": \"struct UptimeData[]\",\n \"name\": \"uptimeData\",\n \"type\": \"tuple[]\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"totalBlocks\",\n \"type\": \"uint256\"\n }\n ],\n \"internalType\": \"struct Uptime\",\n \"name\": \"uptime\",\n \"type\": \"tuple\"\n }\n ],\n \"name\": \"commitEpoch\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"curEpochId\",\n \"type\": \"uint256\"\n },\n {\n \"components\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"startBlock\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"endBlock\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"bytes32\",\n \"name\": \"epochRoot\",\n \"type\": \"bytes32\"\n }\n ],\n \"internalType\": \"struct Epoch\",\n \"name\": \"epoch\",\n \"type\": \"tuple\"\n },\n {\n \"components\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"epochId\",\n \"type\": \"uint256\"\n },\n {\n \"components\": [\n {\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"signedBlocks\",\n \"type\": \"uint256\"\n }\n ],\n \"internalType\": \"struct UptimeData[]\",\n \"name\": \"uptimeData\",\n \"type\": \"tuple[]\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"totalBlocks\",\n \"type\": \"uint256\"\n }\n ],\n \"internalType\": \"struct Uptime\",\n \"name\": \"uptime\",\n \"type\": \"tuple\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"blockNumber\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"pbftRound\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"epochId\",\n \"type\": \"uint256\"\n },\n {\n \"components\": [\n {\n \"internalType\": \"bytes32\",\n \"name\": \"blockHash\",\n \"type\": \"bytes32\"\n },\n {\n \"internalType\": \"bytes\",\n \"name\": \"bitmap\",\n \"type\": \"bytes\"\n },\n {\n \"internalType\": \"bytes\",\n \"name\": \"signature\",\n \"type\": \"bytes\"\n }\n ],\n \"internalType\": \"struct IChildValidatorSetBase.DoubleSignerSlashingInput[]\",\n \"name\": \"inputs\",\n \"type\": \"tuple[]\"\n }\n ],\n \"name\": \"commitEpochWithDoubleSignerSlashing\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"currentEpochId\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"bool\",\n \"name\": \"restake\",\n \"type\": \"bool\"\n }\n ],\n \"name\": \"delegate\",\n \"outputs\": [],\n \"stateMutability\": \"payable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"address\",\n \"name\": \"delegator\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"delegationOf\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"doubleSignerSlashes\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"epochEndBlocks\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"epochReward\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"epochSize\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"epochs\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"startBlock\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"endBlock\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"bytes32\",\n \"name\": \"epochRoot\",\n \"type\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"getCurrentValidatorSet\",\n \"outputs\": [\n {\n \"internalType\": \"address[]\",\n \"name\": \"\",\n \"type\": \"address[]\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"address\",\n \"name\": \"delegator\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"getDelegatorReward\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"blockNumber\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"getEpochByBlock\",\n \"outputs\": [\n {\n \"components\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"startBlock\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"endBlock\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"bytes32\",\n \"name\": \"epochRoot\",\n \"type\": \"bytes32\"\n }\n ],\n \"internalType\": \"struct Epoch\",\n \"name\": \"\",\n \"type\": \"tuple\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"getValidator\",\n \"outputs\": [\n {\n \"components\": [\n {\n \"internalType\": \"uint256[4]\",\n \"name\": \"blsKey\",\n \"type\": \"uint256[4]\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"stake\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"totalStake\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"commission\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"withdrawableRewards\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"bool\",\n \"name\": \"active\",\n \"type\": \"bool\"\n }\n ],\n \"internalType\": \"struct Validator\",\n \"name\": \"\",\n \"type\": \"tuple\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"getValidatorReward\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"components\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"epochReward\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"minStake\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"minDelegation\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"epochSize\",\n \"type\": \"uint256\"\n }\n ],\n \"internalType\": \"struct IChildValidatorSetBase.InitStruct\",\n \"name\": \"init\",\n \"type\": \"tuple\"\n },\n {\n \"internalType\": \"address[]\",\n \"name\": \"validatorAddresses\",\n \"type\": \"address[]\"\n },\n {\n \"internalType\": \"uint256[4][]\",\n \"name\": \"validatorPubkeys\",\n \"type\": \"uint256[4][]\"\n },\n {\n \"internalType\": \"uint256[]\",\n \"name\": \"validatorStakes\",\n \"type\": \"uint256[]\"\n },\n {\n \"internalType\": \"contract IBLS\",\n \"name\": \"newBls\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"uint256[2]\",\n \"name\": \"newMessage\",\n \"type\": \"uint256[2]\"\n },\n {\n \"internalType\": \"address\",\n \"name\": \"governance\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"initialize\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"message\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"minDelegation\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"minStake\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"owner\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"pendingOwner\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"account\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"pendingWithdrawals\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256[2]\",\n \"name\": \"signature\",\n \"type\": \"uint256[2]\"\n },\n {\n \"internalType\": \"uint256[4]\",\n \"name\": \"pubkey\",\n \"type\": \"uint256[4]\"\n }\n ],\n \"name\": \"register\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address[]\",\n \"name\": \"whitelistAddreses\",\n \"type\": \"address[]\"\n }\n ],\n \"name\": \"removeFromWhitelist\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"renounceOwnership\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"newCommission\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"setCommission\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"n\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"sortedValidators\",\n \"outputs\": [\n {\n \"internalType\": \"address[]\",\n \"name\": \"\",\n \"type\": \"address[]\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"stake\",\n \"outputs\": [],\n \"stateMutability\": \"payable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"totalActiveStake\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"activeStake\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"totalStake\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"newOwner\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"transferOwnership\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"validator\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"amount\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"undelegate\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"amount\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"unstake\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"whitelist\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"to\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"withdraw\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"account\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"withdrawable\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"amount\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n }\n ],\n \"bytecode\": \"0x608060405234801561001057600080fd5b50615cf480620000216000396000f3fe6080604052600436106103355760003560e01c8063715018a6116101ab578063a90049d0116100f7578063daacab0911610095578063ea0fee4f1161006f578063ea0fee4f14610967578063eacdc5ff1461097c578063f2fde38b14610992578063f3f43703146109b257600080fd5b8063daacab0914610913578063e0563ab114610933578063e30c39781461094957600080fd5b8063bd8499af116100d1578063bd8499af14610866578063c6b61e4c1461087c578063ce513b6f146108d3578063d1e8507b146108f357600080fd5b8063a90049d014610826578063ae97dde8146103cc578063b16a50391461084657600080fd5b8063947287cf11610164578063982ef0a71161013e578063982ef0a7146107b95780639a91e6d7146107cc5780639b19251a146107e1578063a77af2291461081157600080fd5b8063947287cf1461076c57806395b0b0271461078257806397e5230d146107a257600080fd5b8063715018a61461069d57806378f61932146106b257806379ba5097146107045780637f649783146107195780638b0e9f3f146107395780638da5cb5b1461074e57600080fd5b8063375b3c0a116102855780634d99dd1611610223578063548db174116101fd578063548db174146106275780635689762f14610647578063572d356e14610667578063628da5271461067d57600080fd5b80634d99dd16146105cc57806351351d53146105ec57806351cff8d91461060757600080fd5b80633d6aa5e11161025f5780633d6aa5e11461052e5780633fd500011461054a578063410899c91461056a57806346df33d21461058a57600080fd5b8063375b3c0a146104fa5780633a4b66f1146105105780633b878c221461051857600080fd5b80632153f7fa116102f25780632e17de78116102cc5780632e17de781461046657806333378ada1461048657806334f74d76146104a6578063355e6b43146104da57600080fd5b80632153f7fa14610403578063284017f51461042357806328f731481461045157600080fd5b80630209fdd01461033a57806302985992146103655780631604e416146103895780631904bb2e1461039f5780631bce714d146103cc5780631f6590b8146103e1575b600080fd5b34801561034657600080fd5b5061034f6109d2565b60405161035c9190615196565b60405180910390f35b34801561037157600080fd5b5061037b60055481565b60405190815260200161035c565b34801561039557600080fd5b5061037b60035481565b3480156103ab57600080fd5b506103bf6103ba366004615203565b6109e3565b60405161035c9190615220565b3480156103d857600080fd5b5061037b606481565b3480156103ed57600080fd5b506104016103fc36600461529b565b610a6f565b005b34801561040f57600080fd5b5061034f61041e3660046152d6565b610c32565b34801561042f57600080fd5b5061043961202081565b6040516001600160a01b03909116815260200161035c565b34801561045d57600080fd5b5061037b610d3e565b34801561047257600080fd5b506104016104813660046152d6565b610e08565b34801561049257600080fd5b5061037b6104a1366004615203565b610f25565b3480156104b257600080fd5b5061037b7fbddc396dfed8423aa810557cfed0b5b9e7b7516dac77d0b0cdf3cfbca88518bc81565b3480156104e657600080fd5b506104016104f53660046152d6565b610f3a565b34801561050657600080fd5b5061037b60045481565b610401610fdd565b34801561052457600080fd5b5061043961101081565b34801561053a57600080fd5b5061037b670de0b6b3a764000081565b34801561055657600080fd5b5061037b6105653660046152d6565b6110b0565b34801561057657600080fd5b50610401610585366004615301565b6110d1565b34801561059657600080fd5b506105aa6105a53660046152d6565b61136d565b604080518251815260208084015190820152918101519082015260600161035c565b3480156105d857600080fd5b506104016105e7366004615359565b6113e5565b3480156105f857600080fd5b506104396002600160a01b0381565b34801561061357600080fd5b50610401610622366004615203565b6114f5565b34801561063357600080fd5b506104016106423660046153ca565b6116f1565b34801561065357600080fd5b5061037b61066236600461540c565b61174a565b34801561067357600080fd5b5061037b60005481565b34801561068957600080fd5b5061037b61069836600461540c565b61176a565b3480156106a957600080fd5b5061040161179b565b3480156106be57600080fd5b506106f46106cd366004615445565b61014060209081526000938452604080852082529284528284209052825290205460ff1681565b604051901515815260200161035c565b34801561071057600080fd5b506104016117af565b34801561072557600080fd5b506104016107343660046153ca565b611826565b34801561074557600080fd5b50600b5461037b565b34801561075a57600080fd5b506078546001600160a01b0316610439565b34801561077857600080fd5b5061037b61520881565b34801561078e57600080fd5b50600654610439906001600160a01b031681565b3480156107ae57600080fd5b5061037b620249f081565b6104016107c736600461548c565b61187a565b3480156107d857600080fd5b506104016118e0565b3480156107ed57600080fd5b506106f46107fc366004615203565b60126020526000908152604090205460ff1681565b34801561081d57600080fd5b5061037b600a81565b34801561083257600080fd5b5061040161084136600461548c565b61194d565b34801561085257600080fd5b506104016108613660046154ba565b6119ee565b34801561087257600080fd5b5061037b6101f481565b34801561088857600080fd5b506108b86108973660046152d6565b60116020526000908152604090208054600182015460029092015490919083565b6040805193845260208401929092529082015260600161035c565b3480156108df57600080fd5b5061037b6108ee366004615203565b611c98565b3480156108ff57600080fd5b5061037b61090e3660046152d6565b611cc6565b34801561091f57600080fd5b5061040161092e3660046155a3565b611cdd565b34801561093f57600080fd5b5061043961203081565b34801561095557600080fd5b5060aa546001600160a01b0316610439565b34801561097357600080fd5b5061037b600181565b34801561098857600080fd5b5061037b60015481565b34801561099e57600080fd5b506104016109ad366004615203565b611f9b565b3480156109be57600080fd5b5061037b6109cd366004615203565b61200c565b60606109de6064610c32565b905090565b6109eb61506f565b6109f6600983612033565b604080516101408101909152908160c081018260048282826020028201915b815481526020019060010190808311610a155750505091835250506004820154602082015260058201546040820152600682015460608201526007820154608082015260089091015460ff16151560a09091015292915050565b3360009081526012602052604090205460ff16610ac05760405163973d02cb60e01b815260206004820152600960248201526815d2125511531254d560ba1b60448201526064015b60405180910390fd5b60065460405163ebbdac9160e01b815260009182916001600160a01b039091169063ebbdac9190610afa908790879060079060040161568b565b6040805180830381865afa158015610b16573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b3a91906156cf565b91509150808015610b485750815b610b885760405162461bcd60e51b8152602060048201526011602482015270494e56414c49445f5349474e415455524560781b6044820152606401610ab7565b604080516101408101909152610be29033908060c081018760048282608080828437600092018290525092845250506020820181905260408201819052606082018190526080820152600160a09091015260099190612051565b610beb33612450565b336001600160a01b03167fcab5c47e498c5b85fd42d656842253cfe070f7648c3a198275347b47dec5d27b84604051610c2491906156fe565b60405180910390a250505050565b60606000600960010154831115610c4b57600a54610c4d565b825b905060008167ffffffffffffffff811115610c6a57610c6a61570d565b604051908082528060200260200182016040528015610c93578160200160208202803683370190505b50905081600003610ca5579392505050565b6000610cb16009612499565b90508082600081518110610cc757610cc7615723565b6001600160a01b039092166020928302919091019091015260015b83811015610d3457610cf56009836124fa565b915081838281518110610d0a57610d0a615723565b6001600160a01b039092166020928302919091019091015280610d2c8161574f565b915050610ce2565b5090949350505050565b60008060096001015460641115610d5757600a54610d5a565b60645b905080600003610d6c57600091505090565b6000610d786009612499565b9050610d856009826125fc565b54610d8f826109e3565b60200151610d9d9190615768565b610da79084615768565b925060015b82811015610e0257610dbf6009836124fa565b9150610dcc6009836125fc565b54610dd6836109e3565b60200151610de49190615768565b610dee9085615768565b935080610dfa8161574f565b915050610dac565b50505090565b6000610e15600e3361261a565b336000908152600c6020526040902060070154610e32919061577b565b90506000610e3f83612677565b905081811315610e62576040516396906e6360e01b8152600401610ab79061579b565b6000610e6e82846157f5565b905060045481128015610e8057508015155b15610e9e576040516396906e6360e01b8152600401610ab790615815565b610ea66118e0565b610ec133610eb684600019615868565b600e9190600061268a565b80600003610ee9576000610ed6600933612033565b600801805460ff19169115159190911790555b610ef333856127a1565b60405184815233907f0f5bb82176feb1b5e747e28471aa92156a04d9f3ab9f45f28e2d704232b93f7590602001610c24565b6000610f30826109e3565b6080015192915050565b610f43336109e3565b60a00151610f805760405163973d02cb60e01b81526020600482015260096024820152682b20a624a220aa27a960b91b6044820152606401610ab7565b6064811115610fc65760405162461bcd60e51b815260206004820152601260248201527124a72b20a624a22fa1a7a6a6a4a9a9a4a7a760711b6044820152606401610ab7565b6000610fd3600933612033565b6006019190915550565b610fe6336109e3565b60a001516110235760405163973d02cb60e01b81526020600482015260096024820152682b20a624a220aa27a960b91b6044820152606401610ab7565b336000908152600c60205260409020600701546004546110438234615768565b1015611062576040516396906e6360e01b8152600401610ab790615898565b61106a6118e0565b611078600e3334600061268a565b60405134815233907f9e71bc8eea02a63969f509818f2dafb9254532904319f9dbda79b67bd34a5f3d9060200160405180910390a250565b600281815481106110c057600080fd5b600091825260209091200154905081565b336002600160a01b03146111155760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b6044820152606401610ab7565b60018054600091826111268361574f565b9190505590508084146111715760405162461bcd60e51b815260206004820152601360248201527215539156141150d5115117d15413d0d217d251606a1b6044820152606401610ab7565b82356020840135116111bb5760405162461bcd60e51b81526020600482015260136024820152721393d7d09313d0d2d4d7d0d3d3535255151151606a1b6044820152606401610ab7565b6000546111cd843560208601356158e1565b6111d8906001615768565b6111e2919061590a565b1561123d5760405162461bcd60e51b815260206004820152602560248201527f45504f43485f4d5553545f42455f444956495349424c455f42595f45504f43486044820152645f53495a4560d81b6064820152608401610ab7565b82356011600061124e6001856158e1565b815260200190815260200160002060010154600161126c9190615768565b146112af5760405162461bcd60e51b8152602060048201526013602482015272494e56414c49445f53544152545f424c4f434b60681b6044820152606401610ab7565b60008181526011602090815260408083209186013560018084018290558735845591870135600280850191909155805492830181559093527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace01919091556113168361280d565b61131e61299f565b83602001358460000135867f0ce8712c4dee4bd5a691f0bc1c39594671591e77395f8ebf6a3fb5f63fbea66a876040013560405161135e91815260200190565b60405180910390a45050505050565b6040805160608101825260008082526020820181905291810182905290611395600284612b2f565b9050601160006113a6836001615768565b81526020019081526020016000206040518060600160405290816000820154815260200160018201548152602001600282015481525050915050919050565b60006113f26009846125fc565b33600090815260058201602052604090205490915080831115611428576040516396906e6360e01b8152600401610ab79061591e565b611433823385612be4565b600061143f84836158e1565b90506005548110801561145157508015155b1561146f576040516396906e6360e01b8152600401610ab790615948565b61147a85600061194d565b600061148585612677565b90506114a386600061149984600019615868565b600e92919061268a565b6114ad33866127a1565b6040518581526001600160a01b0387169033907f4d10bd049775c77bd7f255195afba5088028ecb3c7c277d393ccff7934f2f92c9060200160405180910390a3505050505050565b6114fd612c79565b61152d6040518060400160405280600f81526020016e496e697469616c2062616c616e636560881b815250612cd2565b61153e61153947612d15565b612cd2565b6001600160a01b038116611554576115546159a3565b3360009081526010602052604081206001549091908190611576908490612da8565b915091506115ae6040518060400160405280601381526020017215da5d1a191c985dd8589b1948185b5bdd5b9d606a1b815250612cd2565b6115ba61153983612d15565b8083556040518281526001600160a01b0385169033907f2717ead6b9200dd235aad468c9809ea400fe33ac69b5bfaa6d3e90fc922b63989060200160405180910390a36000846001600160a01b03168360405160006040518083038185875af1925050503d806000811461164a576040519150601f19603f3d011682016040523d82523d6000602084013e61164f565b606091505b5050905061165c81612e1a565b6116936040518060400160405280601681526020017542616c616e636520616674657220776974686472617760501b815250612cd2565b61169f61153947612d15565b806116e05760405162461bcd60e51b815260206004820152601160248201527015d2551211149055d05317d19052531151607a1b6044820152606401610ab7565b505050506116ee600160dc55565b50565b6116f9612e62565b60005b818110156117455761173383838381811061171957611719615723565b905060200201602081019061172e9190615203565b612450565b8061173d8161574f565b9150506116fc565b505050565b60006117618261175b6009866125fc565b90612ebc565b90505b92915050565b60006117618261177b6009866125fc565b906001600160a01b03166000908152600591909101602052604090205490565b6117a3612e62565b6117ad6000612eeb565b565b60aa5433906001600160a01b0316811461181d5760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608401610ab7565b6116ee81612eeb565b61182e612e62565b60005b818110156117455761186883838381811061184e5761184e615723565b90506020020160208101906118639190615203565b612f04565b806118728161574f565b915050611831565b60006118876009846125fc565b60058054336000908152918301602052604090912054919250906118ac903490615768565b10156118cb576040516396906e6360e01b8152600401610ab7906159b9565b6118d5838361194d565b611745338434612f50565b60006118ed600933612033565b60078101549091506000819003611902575050565b6000600783015561191333826127a1565b60405181815233907f378c825c0ac073cbae19612eb73cde31c0972f0c8ba2dedb659840f32550df2b906020015b60405180910390a25050565b600061195a6009846125fc565b905060006119688233613016565b9050806000036119785750505050565b821561198e57611989338583612f50565b611998565b61199833826127a1565b821515846001600160a01b0316336001600160a01b03167f6b2cd13395c5d2d1f95934a6a8f884d2cb71e846eb99d6759f7e6a39c060b2d0846040516119e091815260200190565b60405180910390a450505050565b806002811015611a105760405162461bcd60e51b8152600401610ab790615a0a565b611a1a838361305d565b611a5d5760405162461bcd60e51b8152602060048201526014602482015273424c4f434b484153485f4e4f545f554e4951554560601b6044820152606401610ab7565b60005b81811015611b4357611b31878787878786818110611a8057611a80615723565b9050602002810190611a929190615a32565b604080516020810195909552840192909252606083015235608082015260a00160405160208183030381529060405280519060200120858584818110611ada57611ada615723565b9050602002810190611aec9190615a32565b611afa906020810190615a52565b878786818110611b0c57611b0c615723565b9050602002810190611b1e9190615a32565b611b2c906040810190615a52565b61310d565b80611b3b8161574f565b915050611a60565b50600a546000611b5282610c32565b905060008267ffffffffffffffff811115611b6f57611b6f61570d565b604051908082528060200260200182016040528015611b98578160200160208202803683370190505b50905060005b83811015611c7d576000805b86811015611c6857611bed898983818110611bc757611bc7615723565b9050602002810190611bd99190615a32565b611be7906020810190615a52565b85613211565b15611c005781611bfc8161574f565b9250505b6001821115611c5657611c2d858481518110611c1e57611c1e615723565b60200260200101518b8d61327b565b6001848481518110611c4157611c41615723565b91151560209283029190910190910152611c68565b80611c608161574f565b915050611baa565b50508080611c759061574f565b915050611b9e565b50611c8a8c8c8c846133a2565b505050505050505050505050565b6001546001600160a01b03821660009081526010602052604081209091611cbf9190612da8565b5092915050565b60078160028110611cd657600080fd5b0154905081565b604554610100900460ff1615808015611cfd5750604554600160ff909116105b80611d175750303b158015611d17575060455460ff166001145b611d7a5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610ab7565b6045805460ff191660011790558015611d9d576045805461ff0019166101001790555b336002600160a01b0314611de15760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b6044820152606401610ab7565b6001805560608b0135600055611df682612eeb565b611dfe6137b1565b8a3560035560208b013560045560408b013560055560005b89811015611f1e57611e338b8b8381811061184e5761184e615723565b60006040518060c001604052808b8b85818110611e5257611e52615723565b905060800201600480602002604051908101604052809291908260046020028082843760009201919091525050508152602001898985818110611e9757611e97615723565b905060200201358152602001898985818110611eb557611eb5615723565b9050602002013581526020016000815260200160008152602001600115158152509050611f0b8c8c84818110611eed57611eed615723565b9050602002016020810190611f029190615203565b60099083612051565b5080611f168161574f565b915050611e16565b50600680546001600160a01b0319166001600160a01b038616179055611f4760078460026150ad565b508015611f8e576045805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050505050565b611fa3612e62565b60aa80546001600160a01b0383166001600160a01b03199091168117909155611fd46078546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6001546001600160a01b0382166000908152601060205260408120909161176491906137e0565b6001600160a01b031660009081526003918201602052604090200190565b6001600160a01b038216612067576120676159a3565b80602001518160400151101561207f5761207f6159a3565b6120898383613864565b156120b25760405163cda981d560e01b81526001600160a01b0383166004820152602401610ab7565b8060200151600003612138576001600160a01b0382166000908152600380850160205260409091208251839291909101906120f090829060046150e7565b506020820151600482015560408201516005820155606082015160068201556080820151600782015560a0909101516008909101805460ff1916911515919091179055505050565b82546000906001600160a01b03165b6001600160a01b038116156121cf576001600160a01b0381166000908152600386016020526040908190206008015490840151919250829110156121aa576001600160a01b03908116600090815260038601602052604090206001015416612147565b6001600160a01b03908116600090815260038601602052604090206002015416612147565b6040518060a00160405280836001600160a01b0316815260200160006001600160a01b0316815260200160006001600160a01b0316815260200160011515815260200184815250856003016000866001600160a01b03166001600160a01b0316815260200190815260200160002060008201518160000160006101000a8154816001600160a01b0302191690836001600160a01b0316021790555060208201518160010160006101000a8154816001600160a01b0302191690836001600160a01b0316021790555060408201518160020160006101000a8154816001600160a01b0302191690836001600160a01b0316021790555060608201518160020160146101000a81548160ff0219169083151502179055506080820151816003016000820151816000019060046123049291906150e7565b506020820151600482015560408201516005820155606082015160068201556080820151600782015560a0909101516008909101805460ff191691151591909117905550506001600160a01b0382166123755784546001600160a01b0319166001600160a01b03851617855561240b565b6001600160a01b038216600090815260038601602052604090819020600801549084015110156123d7576001600160a01b038281166000908152600387016020526040902060010180546001600160a01b03191691861691909117905561240b565b6001600160a01b038281166000908152600387016020526040902060020180546001600160a01b0319169186169190911790555b61241585856138b5565b6001850180549060006124278361574f565b919050555082602001518560020160008282546124449190615768565b90915550505050505050565b6001600160a01b038116600081815260126020526040808220805460ff19169055517fcdd2e9b91a56913d370075169cefa1602ba36be5301664f752192bb1709df7579190a250565b80546001600160a01b031680156124f5575b6001600160a01b03818116600090815260038401602052604090206002015416156124f5576001600160a01b039081166000908152600383016020526040902060020154166124ab565b919050565b60006001600160a01b038216612523576040516365e52d5160e11b815260040160405180910390fd5b6001600160a01b038281166000908152600385016020526040902060010154161561257a576001600160a01b03808316600090815260038501602052604090206001015461257391859116613c02565b9050611764565b506001600160a01b038082166000908152600384016020526040902054165b6001600160a01b038116158015906125d257506001600160a01b0380821660009081526003850160205260409020600101548382169116145b15611764576001600160a01b03808216600090815260038501602052604090205491925016612599565b6001600160a01b031660009081526004919091016020526040902090565b6001600160a01b03811660009081526020839052604081205461263f57506000611764565b8260010161264d8484613c58565b8154811061265d5761265d615723565b906000526020600020906003020160010154905092915050565b60008181811215611764576117646159a3565b6001600160a01b03831660009081526020859052604081205490819003612734576001808601546126ba91615768565b6001600160a01b0385811660008181526020898152604080832086905580516060810182529384528382018981529084018881526001808d0180548083018255908652939094209451600390930290940180546001600160a01b03191692909516919091178455519083015551600290910155905061279a565b6000856001016127448787613c58565b8154811061275457612754615723565b9060005260206000209060030201905083816001016000828254612778919061577b565b9250508190555082816002016000828254612793919061577b565b9091555050505b5050505050565b6127d281600180546127b39190615768565b6001600160a01b03851660009081526010602052604090209190613c8c565b816001600160a01b03167f655c1cd0236fb6dc4916f34c8ff10e3b18fcaea5b344dfc16c36fbb1bdfc5df28260405161194191815260200190565b6001805461281b91906158e1565b8135146128605760405162461bcd60e51b8152602060048201526013602482015272115413d0d217d393d517d0d3d3535255151151606a1b6044820152606401610ab7565b600061286f6020830183615a99565b915050606481118015906128855750600a548111155b6128a15760405162461bcd60e51b8152600401610ab790615a0a565b60006128ab610d3e565b60035490915060005b8381101561279a5760006128cb6020870187615a99565b838181106128db576128db615723565b9050604002018036038101906128f19190615ae3565b805190915060009061290590600990612033565b90506000612917604089013587615b49565b6020840151845161292a906009906125fc565b54600485015461293a9190615768565b6129449088615b49565b61294e9190615b49565b6129589190615b60565b905060008061296b856000015184613db2565b9150915061297d856000015183613e7c565b84516129899082613ee7565b5050505050806129989061574f565b90506128b4565b600f60005b8154811015612b245760008282815481106129c1576129c1615723565b6000918252602080832060408051606081018252600390940290910180546001600160a01b0316808552600182015493850193909352600201549083015290925090612a0e600983612033565b9050612a1b600983613864565b15612a2b57612a2b600983613f31565b612a4783602001518260040154612a42919061577b565b61431b565b6004820155604083015160208401516005830154612a739291612a699161577b565b612a42919061577b565b6005820155604080516101408101909152612afa908390838160c081018260048282826020028201915b815481526020019060010190808311612a9d5750505091835250506004820154602082015260058201546040820152600682015460608201526007820154608082015260089091015460ff16151560a09091015260099190612051565b506001600160a01b03166000908152600e602052604081205550612b1d8161574f565b90506129a4565b506116ee600e614331565b81546000908103612b4257506000611764565b82546000905b80821015612b8f576000612b5c838361433f565b60008781526020902090915085908201541115612b7b57809150612b89565b612b86816001615768565b92505b50612b48565b600082118015612bbb575083612bb886612baa6001866158e1565b600091825260209091200190565b54145b15612bd457612bcb6001836158e1565b92505050611764565b509050611764565b505092915050565b6001600160a01b038216600090815260058401602052604081208054839290612c0e9084906158e1565b9091555050825481908490600090612c279084906158e1565b90915550506001830154612c4590612c40908390615b49565b612677565b6001600160a01b038316600090815260038501602052604081208054909190612c6f90849061577b565b9091555050505050565b600260dc5403612ccb5760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610ab7565b600260dc55565b6116ee81604051602401612ce69190615b98565b60408051601f198184030181529190526020810180516001600160e01b031663104c13eb60e21b17905261435a565b60606000612d228361437b565b600101905060008167ffffffffffffffff811115612d4257612d4261570d565b6040519080825280601f01601f191660200182016040528015612d6c576020820181803683370190505b5090508181016020015b600019016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a8504945084612d7657509392505050565b81546000905b8360010154811015612e1357600081815260028501602090815260409182902082518084019093528054835260010154908201819052841015612df15750612e13565b8051612dfd9084615768565b9250508080612e0b9061574f565b915050612dae565b9250929050565b60405181151560248201526116ee9060440160408051601f198184030181529190526020810180516001600160e01b03166332458eed60e01b17905261435a565b600160dc55565b6078546001600160a01b031633146117ad5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610ab7565b6001600160a01b0381166000908152600483016020526040812054612ee18484614453565b61176191906158e1565b60aa80546001600160a01b03191690556116ee816144cd565b6001600160a01b038116600081815260126020526040808220805460ff19166001179055517fa850ae9193f515cbae8d35e8925bd2be26627fc91bce650b8652ed254e9cab039190a250565b612f59826109e3565b60a00151612f9e5760405163973d02cb60e01b815260206004820152601160248201527024a72b20a624a22fab20a624a220aa27a960791b6044820152606401610ab7565b612fad82600061149984612677565b612fc48382612fbd6009866125fc565b919061451f565b816001600160a01b0316836001600160a01b03167fe5541a6b6103d4fa7e021ed54fad39c66f27a76bd13d374cf6240ae6bd0bb72b8360405161300991815260200190565b60405180910390a3505050565b60006130228383612ebc565b6001600160a01b0383166000908152600485016020526040812080549293508392909190613051908490615768565b90915550909392505050565b600081815b81811015613102576000613077826001615768565b90505b828110156130ef5785858281811061309457613094615723565b90506020028101906130a69190615a32565b358686848181106130b9576130b9615723565b90506020028101906130cb9190615a32565b35036130dd5760009350505050611764565b806130e78161574f565b91505061307a565b50806130fa8161574f565b915050613062565b506001949350505050565b6000806120306001600160a01b0316620249f08888888888604051602001613139959493929190615bf4565b60408051601f198184030181529082905261315391615c2d565b6000604051808303818686fa925050503d806000811461318f576040519150601f19603f3d011682016040523d82523d6000602084013e613194565b606091505b50915091506000818060200190518101906131af9190615c3f565b90508280156131bb5750805b6132075760405162461bcd60e51b815260206004820152601d60248201527f5349474e41545552455f564552494649434154494f4e5f4641494c45440000006044820152606401610ab7565b5050505050505050565b60008061321f600884615b60565b9050600061322e60088561590a565b905084821061324257600092505050613274565b6000600160ff83161b87878581811061325d5761325d615723565b9050013560f81c60f81b60f81c60ff161611925050505b9392505050565b60008281526101406020908152604080832084845282528083206001600160a01b038716845290915290205460ff16156132b457505050565b60008281526101406020908152604080832084845282528083206001600160a01b03871684529091528120805460ff191660011790556132f5600985612033565b60058101546004820154919250906064613310600a84615b49565b61331a9190615b60565b83600501600082825461332d91906158e1565b9091555060649050613340600a83615b49565b61334a9190615b60565b83600401600082825461335d91906158e1565b9091555050604051849086906001600160a01b038916907f2d7432ca38933aadba9547f33f0568008e240e52e66f1a16c6182e742bce3b1390600090a4505050505050565b60018054600091826133b38361574f565b9190505590508085146133fe5760405162461bcd60e51b815260206004820152601360248201527215539156141150d5115117d15413d0d217d251606a1b6044820152606401610ab7565b83356020850135116134485760405162461bcd60e51b81526020600482015260136024820152721393d7d09313d0d2d4d7d0d3d3535255151151606a1b6044820152606401610ab7565b8335601160006134596001856158e1565b81526020019081526020016000206001015460016134779190615768565b146134ba5760405162461bcd60e51b8152602060048201526013602482015272494e56414c49445f53544152545f424c4f434b60681b6044820152606401610ab7565b600081815260116020908152604080832087830135600180830182905589358355928901356002808401919091558054938401815585527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace90920191909155919061352790860186615a99565b9150506064811180159061353d5750600a548111155b6135595760405162461bcd60e51b8152600401610ab790615a0a565b6000613563610d3e565b90506000805460646135759190615b49565b613584893560208b01356158e1565b6003546135919190615b49565b61359c906064615b49565b6135a69190615b60565b905060005b83811015613755578681815181106135c5576135c5615723565b60200260200101516137455760006135e060208a018a615a99565b838181106135f0576135f0615723565b9050604002018036038101906136069190615ae3565b805190915060009061361a90600990612033565b9050600061362c60408c013587615b49565b6020840151845161363f906009906125fc565b54600485015461364f9190615768565b6136599088615b49565b6136639190615b49565b61366d9190615b60565b9050600080613680856000015184613db2565b91509150818460070160008282546136989190615768565b909155505084516040518481526001600160a01b03909116907f65da9d8bbc1fc112cfb499f15f17b3bfe4c5070dfca6a3e30f7f5f7f17bd86849060200160405180910390a284516136f89082906136f2906009906125fc565b906145a5565b84600001516001600160a01b03167f520e62b73478b262ede881cd483440f8e7b8a4651b0ff9154aeb3510089ddbac8260405161373791815260200190565b60405180910390a250505050505b61374e8161574f565b90506135ab565b5061375e61299f565b876020013588600001358a7f0ce8712c4dee4bd5a691f0bc1c39594671591e77395f8ebf6a3fb5f63fbea66a8b6040013560405161379e91815260200190565b60405180910390a4505050505050505050565b604554610100900460ff166137d85760405162461bcd60e51b8152600401610ab790615c5c565b6117ad61461e565b600080600184600101546137f491906158e1565b90505b83548110611cbf5760008181526002850160209081526040918290208251808401909352805483526001015490820181905284106138355750611cbf565b80516138419084615768565b9250816000036138515750611cbf565b508061385c81615ca7565b9150506137f7565b60006001600160a01b03821615801590611761575082546001600160a01b03838116911614806117615750506001600160a01b03908116600090815260039290920160205260409091205416151590565b60005b82546001600160a01b0383811691161480159061390257506001600160a01b0382811660009081526003850160205260408082205490921681522060020154600160a01b900460ff165b15613bd4576001600160a01b03808316600090815260038501602052604080822054831680835281832054841683529120600101549091168103613a8a576001600160a01b0381811660009081526003860160205260408082205483168252808220600290810154909316808352912090910154909250600160a01b900460ff16156139eb576001600160a01b0380821660008181526003870160205260408082206002808201805460ff60a01b1990811690915588871685528385208201805482169055825487168552928420018054909216600160a01b1790915591905254169250613bce565b6001600160a01b038082166000908152600386016020526040902060020154811690841603613a2157809250613a218484614645565b506001600160a01b0380831660009081526003850160205260408082205483168083528183206002808201805460ff60a01b19908116909155825487168652938520018054909316600160a01b1790925591829052549091613a85918691166147f8565b613bce565b6001600160a01b03818116600090815260038601602052604080822054831682528082206001015490921680825291902060020154909250600160a01b900460ff1615613b34576001600160a01b0380821660008181526003870160205260408082206002808201805460ff60a01b1990811690915588871685528385208201805482169055825487168552928420018054909216600160a01b1790915591905254169250613bce565b6001600160a01b038082166000908152600386016020526040902060010154811690841603613b6a57809250613b6a84846147f8565b506001600160a01b0380831660009081526003850160205260408082205483168083528183206002808201805460ff60a01b19908116909155825487168652938520018054909316600160a01b1790925591829052549091613bce91869116614645565b506138b8565b505080546001600160a01b03166000908152600390910160205260409020600201805460ff60a01b19169055565b60005b6001600160a01b0382811660009081526003850160205260409020600201541615613c52576001600160a01b03918216600090815260038401602052604090206002015490911690613c05565b50919050565b6001600160a01b03811660009081526020839052604081205490819003613c8157613c816159a3565b6117616001826158e1565b81600003613c9c57613c9c6159a3565b82546001840154818103613cf3576040805180820182528581526020808201868152600085815260028a019092529281209151825591516001918201558601805491613ce78361574f565b91905055505050505050565b60006002860181613d056001856158e1565b815260200190815260200160002060010154905080841015613d2957613d296159a3565b83811015613d78576040805180820182528681526020808201878152600086815260028b019092529281209151825591516001918201558701805491613d6e8361574f565b9190505550613daa565b84600287016000613d8a6001866158e1565b815260200190815260200160002060000160008282546127939190615768565b505050505050565b6000806000613dc0856109e3565b60208101519091506000613dd56009886125fc565b5490506000829003613df05760008094509450505050612e13565b80600003613e075785600094509450505050612e13565b6000613e138284615768565b613e1d8489615b49565b613e279190615b60565b90506000613e3582896158e1565b905060006064828760600151613e4b9190615b49565b613e559190615b60565b9050613e618184615768565b613e6b82846158e1565b975097505050505050509250929050565b6000613e89600984612033565b905081816007016000828254613e9f9190615768565b90915550506040518281526001600160a01b038416907f65da9d8bbc1fc112cfb499f15f17b3bfe4c5070dfca6a3e30f7f5f7f17bd86849060200160405180910390a2505050565b613ef6816136f26009856125fc565b816001600160a01b03167f520e62b73478b262ede881cd483440f8e7b8a4651b0ff9154aeb3510089ddbac8260405161194191815260200190565b6001600160a01b038116613f4757613f476159a3565b613f518282613864565b613f7957604051634b2990ed60e11b81526001600160a01b0382166004820152602401610ab7565b6001600160a01b03818116600090815260038401602052604081206001015490918291161580613fc657506001600160a01b03838116600090815260038601602052604090206002015416155b15613fd257508161403e565b506001600160a01b038083166000908152600385016020526040902060020154165b6001600160a01b038181166000908152600386016020526040902060010154161561403e576001600160a01b03908116600090815260038501602052604090206001015416613ff4565b6001600160a01b038181166000908152600386016020526040902060010154161561408a576001600160a01b0380821660009081526003860160205260409020600101541691506140ad565b6001600160a01b0380821660009081526003860160205260409020600201541691505b6001600160a01b038082166000908152600386016020526040808220548584168352912080546001600160a01b031916919092169081179091558015614186576001600160a01b03808216600090815260038701602052604090206001015481169083160361414e576001600160a01b038181166000908152600387016020526040902060010180546001600160a01b0319169185169190911790556141a0565b6001600160a01b038181166000908152600387016020526040902060020180546001600160a01b0319169185169190911790556141a0565b84546001600160a01b0319166001600160a01b0384161785555b6001600160a01b038281166000818152600388016020526040902060020154600160a01b900460ff16159186161461427b576141dd8684876149ab565b6001600160a01b03858116600090815260038801602052604080822060018082015488861680865284862092830180549288166001600160a01b03199384168117909155865284862080548316821790556002938401805494909301805494909716938216841787559285529284208054909316821790925590549152815460ff60a01b1916600160a01b9182900460ff1615159091021790559193915b801561428b5761428b8685614aa3565b6001600160a01b0383166000908152600387016020526040812080546001600160a01b0319908116825560018083018054909216909155600290910180546001600160a81b031916905587018054916142e383615ca7565b90915550506001600160a01b0383166000908152600387016020526040812060070154600288018054919290916127939084906158e1565b60008082121561432d5761432d6159a3565b5090565b6116ee600182016000615115565b600061434e6002848418615b60565b61176190848416615768565b80516a636f6e736f6c652e6c6f67602083016000808483855afa5050505050565b60008072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b83106143ba5772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef810000000083106143e6576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc10000831061440457662386f26fc10000830492506010015b6305f5e100831061441c576305f5e100830492506008015b612710831061443057612710830492506004015b60648310614442576064830492506002015b600a83106117645760010192915050565b6001600160a01b03811660009081526005830160205260408120546001840154829161448391612c409190615b49565b6001600160a01b0384166000908152600386016020526040812054919250906144b090612a42908461577b565b90506144c4670de0b6b3a764000082615b60565b95945050505050565b607880546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6001600160a01b038216600090815260058401602052604081208054839290614549908490615768565b9091555050825481908490600090614562908490615768565b9091555050600183015461457b90612c40908390615b49565b6001600160a01b038316600090815260038501602052604081208054909190612c6f9084906157f5565b806000036145b1575050565b81546000036145e4576002820154604051636130835f60e01b81526001600160a01b039091166004820152602401610ab7565b81546145f8670de0b6b3a764000083615b49565b6146029190615b60565b8260010160008282546146159190615768565b90915550505050565b604554610100900460ff16612e5b5760405162461bcd60e51b8152600401610ab790615c5c565b6001600160a01b038082166000818152600385016020526040808220600281018054915482871680865293852060010154959094526001600160a01b0319909116938516938417905592169080156146c8576001600160a01b038181166000908152600387016020526040902080546001600160a01b0319169186169190911790555b6001600160a01b038381166000908152600387016020526040902080546001600160a01b03191691841691821790556147195784546001600160a01b0319166001600160a01b0384161785556147ae565b6001600160a01b03808316600090815260038701602052604090206001015481169085160361477a576001600160a01b038281166000908152600387016020526040902060010180546001600160a01b0319169185169190911790556147ae565b6001600160a01b038281166000908152600387016020526040902060020180546001600160a01b0319169185169190911790555b50506001600160a01b039081166000818152600390940160205260408085206001018054939094166001600160a01b03199384168117909455928452919092208054909216179055565b6001600160a01b038082166000818152600385016020526040808220600181018054915482871680865293852060020154959094526001600160a01b03199091169385169384179055921690801561487b576001600160a01b038181166000908152600387016020526040902080546001600160a01b0319169186169190911790555b6001600160a01b038381166000908152600387016020526040902080546001600160a01b03191691841691821790556148cc5784546001600160a01b0319166001600160a01b038416178555614961565b6001600160a01b03808316600090815260038701602052604090206002015481169085160361492d576001600160a01b038281166000908152600387016020526040902060020180546001600160a01b031916918516919091179055614961565b6001600160a01b038281166000908152600387016020526040902060010180546001600160a01b0319169185169190911790555b50506001600160a01b039081166000818152600390940160205260408085206002018054939094166001600160a01b03199384168117909455928452919092208054909216179055565b6001600160a01b038082166000908152600385016020526040808220548584168352912080546001600160a01b0319169190921690811790915580614a085783546001600160a01b0319166001600160a01b038416178455614a9d565b6001600160a01b038082166000908152600386016020526040902060010154811690831603614a69576001600160a01b038181166000908152600386016020526040902060010180546001600160a01b031916918516919091179055614a9d565b6001600160a01b038181166000908152600386016020526040902060020180546001600160a01b0319169185169190911790555b50505050565b60005b82546001600160a01b03838116911614801590614ae857506001600160a01b0382166000908152600384016020526040902060020154600160a01b900460ff16155b15615044576001600160a01b0380831660008181526003860160205260408082205484168083529120600101549092169003614db2576001600160a01b038181166000908152600386016020526040808220600290810154909316808352912090910154909250600160a01b900460ff1615614bcf576001600160a01b0380831660009081526003860160205260408082206002908101805460ff60a01b1990811690915593851683529120018054909116600160a01b179055614bac8482614645565b6001600160a01b0380821660009081526003860160205260409020600201541691505b6001600160a01b0382811660009081526003860160205260408082206001015490921681522060020154600160a01b900460ff16158015614c4257506001600160a01b038281166000908152600386016020526040808220600290810154909316825290200154600160a01b900460ff16155b15614c7d576001600160a01b03821660009081526003850160205260409020600201805460ff60a01b1916600160a01b17905591508161503e565b6001600160a01b038281166000908152600386016020526040808220600290810154909316825290200154600160a01b900460ff16614d32576001600160a01b0380831660008181526003870160205260408082206001810154909416825281206002908101805460ff60a01b19908116909155929091529091018054909116600160a01b179055614d0f84836147f8565b6001600160a01b0380821660009081526003860160205260409020600201541691505b6001600160a01b0380821660009081526003860160205260408082206002908101805487861685528385208301805460ff600160a01b9384900416151590920260ff60a01b19928316178155825482169092559054909416835291200180549091169055614da08482614645565b83546001600160a01b0316925061503e565b6001600160a01b0381811660009081526003860160205260408082206001015490921680825291902060020154909250600160a01b900460ff1615614e62576001600160a01b0380831660009081526003860160205260408082206002908101805460ff60a01b1990811690915593851683529120018054909116600160a01b179055614e3f84826147f8565b6001600160a01b0380821660009081526003860160205260409020600101541691505b6001600160a01b038281166000908152600386016020526040808220600290810154909316825290200154600160a01b900460ff16158015614ed557506001600160a01b0382811660009081526003860160205260408082206001015490921681522060020154600160a01b900460ff16155b15614f10576001600160a01b03821660009081526003850160205260409020600201805460ff60a01b1916600160a01b17905591508161503e565b6001600160a01b0382811660009081526003860160205260408082206001015490921681522060020154600160a01b900460ff16614fbd576001600160a01b03808316600081815260038701602052604080822060029081018054909516835290822001805460ff60a01b19908116909155919052815416600160a01b179055614f9a8483614645565b6001600160a01b0380821660009081526003860160205260409020600101541691505b6001600160a01b038082166000908152600386016020526040808220600290810180548786168552838520808401805460ff600160a01b9485900416151590930260ff60a01b199384161790558254821690925560019091015490941683529120018054909116905561503084826147f8565b83546001600160a01b031692505b50614aa6565b506001600160a01b03166000908152600390910160205260409020600201805460ff60a01b19169055565b6040518060c00160405280615082615136565b8152602001600081526020016000815260200160008152602001600081526020016000151581525090565b82600281019282156150db579160200282015b828111156150db5782358255916020019190600101906150c0565b5061432d929150615154565b82600481019282156150db579160200282015b828111156150db5782518255916020019190600101906150fa565b50805460008255600302906000526020600020908101906116ee9190615169565b60405180608001604052806004906020820280368337509192915050565b5b8082111561432d5760008155600101615155565b5b8082111561432d5780546001600160a01b0319168155600060018201819055600282015560030161516a565b6020808252825182820181905260009190848201906040850190845b818110156151d75783516001600160a01b0316835292840192918401916001016151b2565b50909695505050505050565b6001600160a01b03811681146116ee57600080fd5b80356124f5816151e3565b60006020828403121561521557600080fd5b8135613274816151e3565b81516101208201908260005b600481101561524b57825182526020928301929091019060010161522c565b50505060208301516080830152604083015160a0830152606083015160c0830152608083015160e083015260a0830151611cbf61010084018215159052565b806040810183101561176457600080fd5b60008060c083850312156152ae57600080fd5b6152b8848461528a565b91508360c0840111156152ca57600080fd5b50926040919091019150565b6000602082840312156152e857600080fd5b5035919050565b600060608284031215613c5257600080fd5b600080600060a0848603121561531657600080fd5b8335925061532785602086016152ef565b9150608084013567ffffffffffffffff81111561534357600080fd5b61534f868287016152ef565b9150509250925092565b6000806040838503121561536c57600080fd5b8235615377816151e3565b946020939093013593505050565b60008083601f84011261539757600080fd5b50813567ffffffffffffffff8111156153af57600080fd5b6020830191508360208260051b8501011115612e1357600080fd5b600080602083850312156153dd57600080fd5b823567ffffffffffffffff8111156153f457600080fd5b61540085828601615385565b90969095509350505050565b6000806040838503121561541f57600080fd5b823561542a816151e3565b9150602083013561543a816151e3565b809150509250929050565b60008060006060848603121561545a57600080fd5b83359250602084013591506040840135615473816151e3565b809150509250925092565b80151581146116ee57600080fd5b6000806040838503121561549f57600080fd5b82356154aa816151e3565b9150602083013561543a8161547e565b600080600080600080600080610120898b0312156154d757600080fd5b883597506154e88a60208b016152ef565b9650608089013567ffffffffffffffff8082111561550557600080fd5b6155118c838d016152ef565b975060a08b0135965060c08b0135955060e08b013594506101008b013591508082111561553d57600080fd5b5061554a8b828c01615385565b999c989b5096995094979396929594505050565b60008083601f84011261557057600080fd5b50813567ffffffffffffffff81111561558857600080fd5b6020830191508360208260071b8501011115612e1357600080fd5b6000806000806000806000806000808a8c036101608112156155c457600080fd5b60808112156155d257600080fd5b508a995060808b013567ffffffffffffffff808211156155f157600080fd5b6155fd8e838f01615385565b909b50995060a08d013591508082111561561657600080fd5b6156228e838f0161555e565b909950975060c08d013591508082111561563b57600080fd5b506156488d828e01615385565b909650945061565b905060e08c016151f8565b925061566b8c6101008d0161528a565b915061567a6101408c016151f8565b90509295989b9194979a5092959850565b61010081016040858337608084604084013760c082018360005b60028110156156c45781548352602090920191600191820191016156a5565b505050949350505050565b600080604083850312156156e257600080fd5b82516156ed8161547e565b602084015190925061543a8161547e565b60808181019083833792915050565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b60006001820161576157615761615739565b5060010190565b8082018082111561176457611764615739565b8082018281126000831280158216821582161715612bdc57612bdc615739565b6040815260006157c2604083016007815266756e7374616b6560c81b602082015260400190565b8281036020840152613274816014815273494e53554646494349454e545f42414c414e434560601b602082015260400190565b8181036000831280158383131683831282161715611cbf57611cbf615739565b60408152600061583c604083016007815266756e7374616b6560c81b602082015260400190565b828103602084015261327481600d81526c5354414b455f544f4f5f4c4f5760981b602082015260400190565b80820260008212600160ff1b8414161561588457615884615739565b818105831482151761176457611764615739565b6040815260056040820152647374616b6560d81b606082015260806020820152600061176460808301600d81526c5354414b455f544f4f5f4c4f5760981b602082015260400190565b8181038181111561176457611764615739565b634e487b7160e01b600052601260045260246000fd5b600082615919576159196158f4565b500690565b6040815260006157c260408301600a815269756e64656c656761746560b01b602082015260400190565b60408152600061597260408301600a815269756e64656c656761746560b01b602082015260400190565b828103602084015261327481601281527144454c45474154494f4e5f544f4f5f4c4f5760701b602082015260400190565b634e487b7160e01b600052600160045260246000fd5b60408152600860408201526764656c656761746560c01b606082015260806020820152600061176460808301601281527144454c45474154494f4e5f544f4f5f4c4f5760701b602082015260400190565b6020808252600e908201526d0929cac82989288be988a9c8ea8960931b604082015260600190565b60008235605e19833603018112615a4857600080fd5b9190910192915050565b6000808335601e19843603018112615a6957600080fd5b83018035915067ffffffffffffffff821115615a8457600080fd5b602001915036819003821315612e1357600080fd5b6000808335601e19843603018112615ab057600080fd5b83018035915067ffffffffffffffff821115615acb57600080fd5b6020019150600681901b3603821315612e1357600080fd5b600060408284031215615af557600080fd5b6040516040810181811067ffffffffffffffff82111715615b2657634e487b7160e01b600052604160045260246000fd5b6040528235615b34816151e3565b81526020928301359281019290925250919050565b808202811582820484141761176457611764615739565b600082615b6f57615b6f6158f4565b500490565b60005b83811015615b8f578181015183820152602001615b77565b50506000910152565b6020815260008251806020840152615bb7816040850160208701615b74565b601f01601f19169190910160400192915050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b858152606060208201526000615c0e606083018688615bcb565b8281036040840152615c21818587615bcb565b98975050505050505050565b60008251615a48818460208701615b74565b600060208284031215615c5157600080fd5b81516132748161547e565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b600081615cb657615cb6615739565b50600019019056fea2646970667358221220ad1a6c9a34c722360776fbfc24b96b9a0167062a335b9a824fcce67bc1a110a964736f6c63430008110033\",\n \"deployedBytecode\": \"0x6080604052600436106103355760003560e01c8063715018a6116101ab578063a90049d0116100f7578063daacab0911610095578063ea0fee4f1161006f578063ea0fee4f14610967578063eacdc5ff1461097c578063f2fde38b14610992578063f3f43703146109b257600080fd5b8063daacab0914610913578063e0563ab114610933578063e30c39781461094957600080fd5b8063bd8499af116100d1578063bd8499af14610866578063c6b61e4c1461087c578063ce513b6f146108d3578063d1e8507b146108f357600080fd5b8063a90049d014610826578063ae97dde8146103cc578063b16a50391461084657600080fd5b8063947287cf11610164578063982ef0a71161013e578063982ef0a7146107b95780639a91e6d7146107cc5780639b19251a146107e1578063a77af2291461081157600080fd5b8063947287cf1461076c57806395b0b0271461078257806397e5230d146107a257600080fd5b8063715018a61461069d57806378f61932146106b257806379ba5097146107045780637f649783146107195780638b0e9f3f146107395780638da5cb5b1461074e57600080fd5b8063375b3c0a116102855780634d99dd1611610223578063548db174116101fd578063548db174146106275780635689762f14610647578063572d356e14610667578063628da5271461067d57600080fd5b80634d99dd16146105cc57806351351d53146105ec57806351cff8d91461060757600080fd5b80633d6aa5e11161025f5780633d6aa5e11461052e5780633fd500011461054a578063410899c91461056a57806346df33d21461058a57600080fd5b8063375b3c0a146104fa5780633a4b66f1146105105780633b878c221461051857600080fd5b80632153f7fa116102f25780632e17de78116102cc5780632e17de781461046657806333378ada1461048657806334f74d76146104a6578063355e6b43146104da57600080fd5b80632153f7fa14610403578063284017f51461042357806328f731481461045157600080fd5b80630209fdd01461033a57806302985992146103655780631604e416146103895780631904bb2e1461039f5780631bce714d146103cc5780631f6590b8146103e1575b600080fd5b34801561034657600080fd5b5061034f6109d2565b60405161035c9190615196565b60405180910390f35b34801561037157600080fd5b5061037b60055481565b60405190815260200161035c565b34801561039557600080fd5b5061037b60035481565b3480156103ab57600080fd5b506103bf6103ba366004615203565b6109e3565b60405161035c9190615220565b3480156103d857600080fd5b5061037b606481565b3480156103ed57600080fd5b506104016103fc36600461529b565b610a6f565b005b34801561040f57600080fd5b5061034f61041e3660046152d6565b610c32565b34801561042f57600080fd5b5061043961202081565b6040516001600160a01b03909116815260200161035c565b34801561045d57600080fd5b5061037b610d3e565b34801561047257600080fd5b506104016104813660046152d6565b610e08565b34801561049257600080fd5b5061037b6104a1366004615203565b610f25565b3480156104b257600080fd5b5061037b7fbddc396dfed8423aa810557cfed0b5b9e7b7516dac77d0b0cdf3cfbca88518bc81565b3480156104e657600080fd5b506104016104f53660046152d6565b610f3a565b34801561050657600080fd5b5061037b60045481565b610401610fdd565b34801561052457600080fd5b5061043961101081565b34801561053a57600080fd5b5061037b670de0b6b3a764000081565b34801561055657600080fd5b5061037b6105653660046152d6565b6110b0565b34801561057657600080fd5b50610401610585366004615301565b6110d1565b34801561059657600080fd5b506105aa6105a53660046152d6565b61136d565b604080518251815260208084015190820152918101519082015260600161035c565b3480156105d857600080fd5b506104016105e7366004615359565b6113e5565b3480156105f857600080fd5b506104396002600160a01b0381565b34801561061357600080fd5b50610401610622366004615203565b6114f5565b34801561063357600080fd5b506104016106423660046153ca565b6116f1565b34801561065357600080fd5b5061037b61066236600461540c565b61174a565b34801561067357600080fd5b5061037b60005481565b34801561068957600080fd5b5061037b61069836600461540c565b61176a565b3480156106a957600080fd5b5061040161179b565b3480156106be57600080fd5b506106f46106cd366004615445565b61014060209081526000938452604080852082529284528284209052825290205460ff1681565b604051901515815260200161035c565b34801561071057600080fd5b506104016117af565b34801561072557600080fd5b506104016107343660046153ca565b611826565b34801561074557600080fd5b50600b5461037b565b34801561075a57600080fd5b506078546001600160a01b0316610439565b34801561077857600080fd5b5061037b61520881565b34801561078e57600080fd5b50600654610439906001600160a01b031681565b3480156107ae57600080fd5b5061037b620249f081565b6104016107c736600461548c565b61187a565b3480156107d857600080fd5b506104016118e0565b3480156107ed57600080fd5b506106f46107fc366004615203565b60126020526000908152604090205460ff1681565b34801561081d57600080fd5b5061037b600a81565b34801561083257600080fd5b5061040161084136600461548c565b61194d565b34801561085257600080fd5b506104016108613660046154ba565b6119ee565b34801561087257600080fd5b5061037b6101f481565b34801561088857600080fd5b506108b86108973660046152d6565b60116020526000908152604090208054600182015460029092015490919083565b6040805193845260208401929092529082015260600161035c565b3480156108df57600080fd5b5061037b6108ee366004615203565b611c98565b3480156108ff57600080fd5b5061037b61090e3660046152d6565b611cc6565b34801561091f57600080fd5b5061040161092e3660046155a3565b611cdd565b34801561093f57600080fd5b5061043961203081565b34801561095557600080fd5b5060aa546001600160a01b0316610439565b34801561097357600080fd5b5061037b600181565b34801561098857600080fd5b5061037b60015481565b34801561099e57600080fd5b506104016109ad366004615203565b611f9b565b3480156109be57600080fd5b5061037b6109cd366004615203565b61200c565b60606109de6064610c32565b905090565b6109eb61506f565b6109f6600983612033565b604080516101408101909152908160c081018260048282826020028201915b815481526020019060010190808311610a155750505091835250506004820154602082015260058201546040820152600682015460608201526007820154608082015260089091015460ff16151560a09091015292915050565b3360009081526012602052604090205460ff16610ac05760405163973d02cb60e01b815260206004820152600960248201526815d2125511531254d560ba1b60448201526064015b60405180910390fd5b60065460405163ebbdac9160e01b815260009182916001600160a01b039091169063ebbdac9190610afa908790879060079060040161568b565b6040805180830381865afa158015610b16573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b3a91906156cf565b91509150808015610b485750815b610b885760405162461bcd60e51b8152602060048201526011602482015270494e56414c49445f5349474e415455524560781b6044820152606401610ab7565b604080516101408101909152610be29033908060c081018760048282608080828437600092018290525092845250506020820181905260408201819052606082018190526080820152600160a09091015260099190612051565b610beb33612450565b336001600160a01b03167fcab5c47e498c5b85fd42d656842253cfe070f7648c3a198275347b47dec5d27b84604051610c2491906156fe565b60405180910390a250505050565b60606000600960010154831115610c4b57600a54610c4d565b825b905060008167ffffffffffffffff811115610c6a57610c6a61570d565b604051908082528060200260200182016040528015610c93578160200160208202803683370190505b50905081600003610ca5579392505050565b6000610cb16009612499565b90508082600081518110610cc757610cc7615723565b6001600160a01b039092166020928302919091019091015260015b83811015610d3457610cf56009836124fa565b915081838281518110610d0a57610d0a615723565b6001600160a01b039092166020928302919091019091015280610d2c8161574f565b915050610ce2565b5090949350505050565b60008060096001015460641115610d5757600a54610d5a565b60645b905080600003610d6c57600091505090565b6000610d786009612499565b9050610d856009826125fc565b54610d8f826109e3565b60200151610d9d9190615768565b610da79084615768565b925060015b82811015610e0257610dbf6009836124fa565b9150610dcc6009836125fc565b54610dd6836109e3565b60200151610de49190615768565b610dee9085615768565b935080610dfa8161574f565b915050610dac565b50505090565b6000610e15600e3361261a565b336000908152600c6020526040902060070154610e32919061577b565b90506000610e3f83612677565b905081811315610e62576040516396906e6360e01b8152600401610ab79061579b565b6000610e6e82846157f5565b905060045481128015610e8057508015155b15610e9e576040516396906e6360e01b8152600401610ab790615815565b610ea66118e0565b610ec133610eb684600019615868565b600e9190600061268a565b80600003610ee9576000610ed6600933612033565b600801805460ff19169115159190911790555b610ef333856127a1565b60405184815233907f0f5bb82176feb1b5e747e28471aa92156a04d9f3ab9f45f28e2d704232b93f7590602001610c24565b6000610f30826109e3565b6080015192915050565b610f43336109e3565b60a00151610f805760405163973d02cb60e01b81526020600482015260096024820152682b20a624a220aa27a960b91b6044820152606401610ab7565b6064811115610fc65760405162461bcd60e51b815260206004820152601260248201527124a72b20a624a22fa1a7a6a6a4a9a9a4a7a760711b6044820152606401610ab7565b6000610fd3600933612033565b6006019190915550565b610fe6336109e3565b60a001516110235760405163973d02cb60e01b81526020600482015260096024820152682b20a624a220aa27a960b91b6044820152606401610ab7565b336000908152600c60205260409020600701546004546110438234615768565b1015611062576040516396906e6360e01b8152600401610ab790615898565b61106a6118e0565b611078600e3334600061268a565b60405134815233907f9e71bc8eea02a63969f509818f2dafb9254532904319f9dbda79b67bd34a5f3d9060200160405180910390a250565b600281815481106110c057600080fd5b600091825260209091200154905081565b336002600160a01b03146111155760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b6044820152606401610ab7565b60018054600091826111268361574f565b9190505590508084146111715760405162461bcd60e51b815260206004820152601360248201527215539156141150d5115117d15413d0d217d251606a1b6044820152606401610ab7565b82356020840135116111bb5760405162461bcd60e51b81526020600482015260136024820152721393d7d09313d0d2d4d7d0d3d3535255151151606a1b6044820152606401610ab7565b6000546111cd843560208601356158e1565b6111d8906001615768565b6111e2919061590a565b1561123d5760405162461bcd60e51b815260206004820152602560248201527f45504f43485f4d5553545f42455f444956495349424c455f42595f45504f43486044820152645f53495a4560d81b6064820152608401610ab7565b82356011600061124e6001856158e1565b815260200190815260200160002060010154600161126c9190615768565b146112af5760405162461bcd60e51b8152602060048201526013602482015272494e56414c49445f53544152545f424c4f434b60681b6044820152606401610ab7565b60008181526011602090815260408083209186013560018084018290558735845591870135600280850191909155805492830181559093527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace01919091556113168361280d565b61131e61299f565b83602001358460000135867f0ce8712c4dee4bd5a691f0bc1c39594671591e77395f8ebf6a3fb5f63fbea66a876040013560405161135e91815260200190565b60405180910390a45050505050565b6040805160608101825260008082526020820181905291810182905290611395600284612b2f565b9050601160006113a6836001615768565b81526020019081526020016000206040518060600160405290816000820154815260200160018201548152602001600282015481525050915050919050565b60006113f26009846125fc565b33600090815260058201602052604090205490915080831115611428576040516396906e6360e01b8152600401610ab79061591e565b611433823385612be4565b600061143f84836158e1565b90506005548110801561145157508015155b1561146f576040516396906e6360e01b8152600401610ab790615948565b61147a85600061194d565b600061148585612677565b90506114a386600061149984600019615868565b600e92919061268a565b6114ad33866127a1565b6040518581526001600160a01b0387169033907f4d10bd049775c77bd7f255195afba5088028ecb3c7c277d393ccff7934f2f92c9060200160405180910390a3505050505050565b6114fd612c79565b61152d6040518060400160405280600f81526020016e496e697469616c2062616c616e636560881b815250612cd2565b61153e61153947612d15565b612cd2565b6001600160a01b038116611554576115546159a3565b3360009081526010602052604081206001549091908190611576908490612da8565b915091506115ae6040518060400160405280601381526020017215da5d1a191c985dd8589b1948185b5bdd5b9d606a1b815250612cd2565b6115ba61153983612d15565b8083556040518281526001600160a01b0385169033907f2717ead6b9200dd235aad468c9809ea400fe33ac69b5bfaa6d3e90fc922b63989060200160405180910390a36000846001600160a01b03168360405160006040518083038185875af1925050503d806000811461164a576040519150601f19603f3d011682016040523d82523d6000602084013e61164f565b606091505b5050905061165c81612e1a565b6116936040518060400160405280601681526020017542616c616e636520616674657220776974686472617760501b815250612cd2565b61169f61153947612d15565b806116e05760405162461bcd60e51b815260206004820152601160248201527015d2551211149055d05317d19052531151607a1b6044820152606401610ab7565b505050506116ee600160dc55565b50565b6116f9612e62565b60005b818110156117455761173383838381811061171957611719615723565b905060200201602081019061172e9190615203565b612450565b8061173d8161574f565b9150506116fc565b505050565b60006117618261175b6009866125fc565b90612ebc565b90505b92915050565b60006117618261177b6009866125fc565b906001600160a01b03166000908152600591909101602052604090205490565b6117a3612e62565b6117ad6000612eeb565b565b60aa5433906001600160a01b0316811461181d5760405162461bcd60e51b815260206004820152602960248201527f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865206044820152683732bb9037bbb732b960b91b6064820152608401610ab7565b6116ee81612eeb565b61182e612e62565b60005b818110156117455761186883838381811061184e5761184e615723565b90506020020160208101906118639190615203565b612f04565b806118728161574f565b915050611831565b60006118876009846125fc565b60058054336000908152918301602052604090912054919250906118ac903490615768565b10156118cb576040516396906e6360e01b8152600401610ab7906159b9565b6118d5838361194d565b611745338434612f50565b60006118ed600933612033565b60078101549091506000819003611902575050565b6000600783015561191333826127a1565b60405181815233907f378c825c0ac073cbae19612eb73cde31c0972f0c8ba2dedb659840f32550df2b906020015b60405180910390a25050565b600061195a6009846125fc565b905060006119688233613016565b9050806000036119785750505050565b821561198e57611989338583612f50565b611998565b61199833826127a1565b821515846001600160a01b0316336001600160a01b03167f6b2cd13395c5d2d1f95934a6a8f884d2cb71e846eb99d6759f7e6a39c060b2d0846040516119e091815260200190565b60405180910390a450505050565b806002811015611a105760405162461bcd60e51b8152600401610ab790615a0a565b611a1a838361305d565b611a5d5760405162461bcd60e51b8152602060048201526014602482015273424c4f434b484153485f4e4f545f554e4951554560601b6044820152606401610ab7565b60005b81811015611b4357611b31878787878786818110611a8057611a80615723565b9050602002810190611a929190615a32565b604080516020810195909552840192909252606083015235608082015260a00160405160208183030381529060405280519060200120858584818110611ada57611ada615723565b9050602002810190611aec9190615a32565b611afa906020810190615a52565b878786818110611b0c57611b0c615723565b9050602002810190611b1e9190615a32565b611b2c906040810190615a52565b61310d565b80611b3b8161574f565b915050611a60565b50600a546000611b5282610c32565b905060008267ffffffffffffffff811115611b6f57611b6f61570d565b604051908082528060200260200182016040528015611b98578160200160208202803683370190505b50905060005b83811015611c7d576000805b86811015611c6857611bed898983818110611bc757611bc7615723565b9050602002810190611bd99190615a32565b611be7906020810190615a52565b85613211565b15611c005781611bfc8161574f565b9250505b6001821115611c5657611c2d858481518110611c1e57611c1e615723565b60200260200101518b8d61327b565b6001848481518110611c4157611c41615723565b91151560209283029190910190910152611c68565b80611c608161574f565b915050611baa565b50508080611c759061574f565b915050611b9e565b50611c8a8c8c8c846133a2565b505050505050505050505050565b6001546001600160a01b03821660009081526010602052604081209091611cbf9190612da8565b5092915050565b60078160028110611cd657600080fd5b0154905081565b604554610100900460ff1615808015611cfd5750604554600160ff909116105b80611d175750303b158015611d17575060455460ff166001145b611d7a5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610ab7565b6045805460ff191660011790558015611d9d576045805461ff0019166101001790555b336002600160a01b0314611de15760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b6044820152606401610ab7565b6001805560608b0135600055611df682612eeb565b611dfe6137b1565b8a3560035560208b013560045560408b013560055560005b89811015611f1e57611e338b8b8381811061184e5761184e615723565b60006040518060c001604052808b8b85818110611e5257611e52615723565b905060800201600480602002604051908101604052809291908260046020028082843760009201919091525050508152602001898985818110611e9757611e97615723565b905060200201358152602001898985818110611eb557611eb5615723565b9050602002013581526020016000815260200160008152602001600115158152509050611f0b8c8c84818110611eed57611eed615723565b9050602002016020810190611f029190615203565b60099083612051565b5080611f168161574f565b915050611e16565b50600680546001600160a01b0319166001600160a01b038616179055611f4760078460026150ad565b508015611f8e576045805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050505050565b611fa3612e62565b60aa80546001600160a01b0383166001600160a01b03199091168117909155611fd46078546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6001546001600160a01b0382166000908152601060205260408120909161176491906137e0565b6001600160a01b031660009081526003918201602052604090200190565b6001600160a01b038216612067576120676159a3565b80602001518160400151101561207f5761207f6159a3565b6120898383613864565b156120b25760405163cda981d560e01b81526001600160a01b0383166004820152602401610ab7565b8060200151600003612138576001600160a01b0382166000908152600380850160205260409091208251839291909101906120f090829060046150e7565b506020820151600482015560408201516005820155606082015160068201556080820151600782015560a0909101516008909101805460ff1916911515919091179055505050565b82546000906001600160a01b03165b6001600160a01b038116156121cf576001600160a01b0381166000908152600386016020526040908190206008015490840151919250829110156121aa576001600160a01b03908116600090815260038601602052604090206001015416612147565b6001600160a01b03908116600090815260038601602052604090206002015416612147565b6040518060a00160405280836001600160a01b0316815260200160006001600160a01b0316815260200160006001600160a01b0316815260200160011515815260200184815250856003016000866001600160a01b03166001600160a01b0316815260200190815260200160002060008201518160000160006101000a8154816001600160a01b0302191690836001600160a01b0316021790555060208201518160010160006101000a8154816001600160a01b0302191690836001600160a01b0316021790555060408201518160020160006101000a8154816001600160a01b0302191690836001600160a01b0316021790555060608201518160020160146101000a81548160ff0219169083151502179055506080820151816003016000820151816000019060046123049291906150e7565b506020820151600482015560408201516005820155606082015160068201556080820151600782015560a0909101516008909101805460ff191691151591909117905550506001600160a01b0382166123755784546001600160a01b0319166001600160a01b03851617855561240b565b6001600160a01b038216600090815260038601602052604090819020600801549084015110156123d7576001600160a01b038281166000908152600387016020526040902060010180546001600160a01b03191691861691909117905561240b565b6001600160a01b038281166000908152600387016020526040902060020180546001600160a01b0319169186169190911790555b61241585856138b5565b6001850180549060006124278361574f565b919050555082602001518560020160008282546124449190615768565b90915550505050505050565b6001600160a01b038116600081815260126020526040808220805460ff19169055517fcdd2e9b91a56913d370075169cefa1602ba36be5301664f752192bb1709df7579190a250565b80546001600160a01b031680156124f5575b6001600160a01b03818116600090815260038401602052604090206002015416156124f5576001600160a01b039081166000908152600383016020526040902060020154166124ab565b919050565b60006001600160a01b038216612523576040516365e52d5160e11b815260040160405180910390fd5b6001600160a01b038281166000908152600385016020526040902060010154161561257a576001600160a01b03808316600090815260038501602052604090206001015461257391859116613c02565b9050611764565b506001600160a01b038082166000908152600384016020526040902054165b6001600160a01b038116158015906125d257506001600160a01b0380821660009081526003850160205260409020600101548382169116145b15611764576001600160a01b03808216600090815260038501602052604090205491925016612599565b6001600160a01b031660009081526004919091016020526040902090565b6001600160a01b03811660009081526020839052604081205461263f57506000611764565b8260010161264d8484613c58565b8154811061265d5761265d615723565b906000526020600020906003020160010154905092915050565b60008181811215611764576117646159a3565b6001600160a01b03831660009081526020859052604081205490819003612734576001808601546126ba91615768565b6001600160a01b0385811660008181526020898152604080832086905580516060810182529384528382018981529084018881526001808d0180548083018255908652939094209451600390930290940180546001600160a01b03191692909516919091178455519083015551600290910155905061279a565b6000856001016127448787613c58565b8154811061275457612754615723565b9060005260206000209060030201905083816001016000828254612778919061577b565b9250508190555082816002016000828254612793919061577b565b9091555050505b5050505050565b6127d281600180546127b39190615768565b6001600160a01b03851660009081526010602052604090209190613c8c565b816001600160a01b03167f655c1cd0236fb6dc4916f34c8ff10e3b18fcaea5b344dfc16c36fbb1bdfc5df28260405161194191815260200190565b6001805461281b91906158e1565b8135146128605760405162461bcd60e51b8152602060048201526013602482015272115413d0d217d393d517d0d3d3535255151151606a1b6044820152606401610ab7565b600061286f6020830183615a99565b915050606481118015906128855750600a548111155b6128a15760405162461bcd60e51b8152600401610ab790615a0a565b60006128ab610d3e565b60035490915060005b8381101561279a5760006128cb6020870187615a99565b838181106128db576128db615723565b9050604002018036038101906128f19190615ae3565b805190915060009061290590600990612033565b90506000612917604089013587615b49565b6020840151845161292a906009906125fc565b54600485015461293a9190615768565b6129449088615b49565b61294e9190615b49565b6129589190615b60565b905060008061296b856000015184613db2565b9150915061297d856000015183613e7c565b84516129899082613ee7565b5050505050806129989061574f565b90506128b4565b600f60005b8154811015612b245760008282815481106129c1576129c1615723565b6000918252602080832060408051606081018252600390940290910180546001600160a01b0316808552600182015493850193909352600201549083015290925090612a0e600983612033565b9050612a1b600983613864565b15612a2b57612a2b600983613f31565b612a4783602001518260040154612a42919061577b565b61431b565b6004820155604083015160208401516005830154612a739291612a699161577b565b612a42919061577b565b6005820155604080516101408101909152612afa908390838160c081018260048282826020028201915b815481526020019060010190808311612a9d5750505091835250506004820154602082015260058201546040820152600682015460608201526007820154608082015260089091015460ff16151560a09091015260099190612051565b506001600160a01b03166000908152600e602052604081205550612b1d8161574f565b90506129a4565b506116ee600e614331565b81546000908103612b4257506000611764565b82546000905b80821015612b8f576000612b5c838361433f565b60008781526020902090915085908201541115612b7b57809150612b89565b612b86816001615768565b92505b50612b48565b600082118015612bbb575083612bb886612baa6001866158e1565b600091825260209091200190565b54145b15612bd457612bcb6001836158e1565b92505050611764565b509050611764565b505092915050565b6001600160a01b038216600090815260058401602052604081208054839290612c0e9084906158e1565b9091555050825481908490600090612c279084906158e1565b90915550506001830154612c4590612c40908390615b49565b612677565b6001600160a01b038316600090815260038501602052604081208054909190612c6f90849061577b565b9091555050505050565b600260dc5403612ccb5760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610ab7565b600260dc55565b6116ee81604051602401612ce69190615b98565b60408051601f198184030181529190526020810180516001600160e01b031663104c13eb60e21b17905261435a565b60606000612d228361437b565b600101905060008167ffffffffffffffff811115612d4257612d4261570d565b6040519080825280601f01601f191660200182016040528015612d6c576020820181803683370190505b5090508181016020015b600019016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a8504945084612d7657509392505050565b81546000905b8360010154811015612e1357600081815260028501602090815260409182902082518084019093528054835260010154908201819052841015612df15750612e13565b8051612dfd9084615768565b9250508080612e0b9061574f565b915050612dae565b9250929050565b60405181151560248201526116ee9060440160408051601f198184030181529190526020810180516001600160e01b03166332458eed60e01b17905261435a565b600160dc55565b6078546001600160a01b031633146117ad5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610ab7565b6001600160a01b0381166000908152600483016020526040812054612ee18484614453565b61176191906158e1565b60aa80546001600160a01b03191690556116ee816144cd565b6001600160a01b038116600081815260126020526040808220805460ff19166001179055517fa850ae9193f515cbae8d35e8925bd2be26627fc91bce650b8652ed254e9cab039190a250565b612f59826109e3565b60a00151612f9e5760405163973d02cb60e01b815260206004820152601160248201527024a72b20a624a22fab20a624a220aa27a960791b6044820152606401610ab7565b612fad82600061149984612677565b612fc48382612fbd6009866125fc565b919061451f565b816001600160a01b0316836001600160a01b03167fe5541a6b6103d4fa7e021ed54fad39c66f27a76bd13d374cf6240ae6bd0bb72b8360405161300991815260200190565b60405180910390a3505050565b60006130228383612ebc565b6001600160a01b0383166000908152600485016020526040812080549293508392909190613051908490615768565b90915550909392505050565b600081815b81811015613102576000613077826001615768565b90505b828110156130ef5785858281811061309457613094615723565b90506020028101906130a69190615a32565b358686848181106130b9576130b9615723565b90506020028101906130cb9190615a32565b35036130dd5760009350505050611764565b806130e78161574f565b91505061307a565b50806130fa8161574f565b915050613062565b506001949350505050565b6000806120306001600160a01b0316620249f08888888888604051602001613139959493929190615bf4565b60408051601f198184030181529082905261315391615c2d565b6000604051808303818686fa925050503d806000811461318f576040519150601f19603f3d011682016040523d82523d6000602084013e613194565b606091505b50915091506000818060200190518101906131af9190615c3f565b90508280156131bb5750805b6132075760405162461bcd60e51b815260206004820152601d60248201527f5349474e41545552455f564552494649434154494f4e5f4641494c45440000006044820152606401610ab7565b5050505050505050565b60008061321f600884615b60565b9050600061322e60088561590a565b905084821061324257600092505050613274565b6000600160ff83161b87878581811061325d5761325d615723565b9050013560f81c60f81b60f81c60ff161611925050505b9392505050565b60008281526101406020908152604080832084845282528083206001600160a01b038716845290915290205460ff16156132b457505050565b60008281526101406020908152604080832084845282528083206001600160a01b03871684529091528120805460ff191660011790556132f5600985612033565b60058101546004820154919250906064613310600a84615b49565b61331a9190615b60565b83600501600082825461332d91906158e1565b9091555060649050613340600a83615b49565b61334a9190615b60565b83600401600082825461335d91906158e1565b9091555050604051849086906001600160a01b038916907f2d7432ca38933aadba9547f33f0568008e240e52e66f1a16c6182e742bce3b1390600090a4505050505050565b60018054600091826133b38361574f565b9190505590508085146133fe5760405162461bcd60e51b815260206004820152601360248201527215539156141150d5115117d15413d0d217d251606a1b6044820152606401610ab7565b83356020850135116134485760405162461bcd60e51b81526020600482015260136024820152721393d7d09313d0d2d4d7d0d3d3535255151151606a1b6044820152606401610ab7565b8335601160006134596001856158e1565b81526020019081526020016000206001015460016134779190615768565b146134ba5760405162461bcd60e51b8152602060048201526013602482015272494e56414c49445f53544152545f424c4f434b60681b6044820152606401610ab7565b600081815260116020908152604080832087830135600180830182905589358355928901356002808401919091558054938401815585527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace90920191909155919061352790860186615a99565b9150506064811180159061353d5750600a548111155b6135595760405162461bcd60e51b8152600401610ab790615a0a565b6000613563610d3e565b90506000805460646135759190615b49565b613584893560208b01356158e1565b6003546135919190615b49565b61359c906064615b49565b6135a69190615b60565b905060005b83811015613755578681815181106135c5576135c5615723565b60200260200101516137455760006135e060208a018a615a99565b838181106135f0576135f0615723565b9050604002018036038101906136069190615ae3565b805190915060009061361a90600990612033565b9050600061362c60408c013587615b49565b6020840151845161363f906009906125fc565b54600485015461364f9190615768565b6136599088615b49565b6136639190615b49565b61366d9190615b60565b9050600080613680856000015184613db2565b91509150818460070160008282546136989190615768565b909155505084516040518481526001600160a01b03909116907f65da9d8bbc1fc112cfb499f15f17b3bfe4c5070dfca6a3e30f7f5f7f17bd86849060200160405180910390a284516136f89082906136f2906009906125fc565b906145a5565b84600001516001600160a01b03167f520e62b73478b262ede881cd483440f8e7b8a4651b0ff9154aeb3510089ddbac8260405161373791815260200190565b60405180910390a250505050505b61374e8161574f565b90506135ab565b5061375e61299f565b876020013588600001358a7f0ce8712c4dee4bd5a691f0bc1c39594671591e77395f8ebf6a3fb5f63fbea66a8b6040013560405161379e91815260200190565b60405180910390a4505050505050505050565b604554610100900460ff166137d85760405162461bcd60e51b8152600401610ab790615c5c565b6117ad61461e565b600080600184600101546137f491906158e1565b90505b83548110611cbf5760008181526002850160209081526040918290208251808401909352805483526001015490820181905284106138355750611cbf565b80516138419084615768565b9250816000036138515750611cbf565b508061385c81615ca7565b9150506137f7565b60006001600160a01b03821615801590611761575082546001600160a01b03838116911614806117615750506001600160a01b03908116600090815260039290920160205260409091205416151590565b60005b82546001600160a01b0383811691161480159061390257506001600160a01b0382811660009081526003850160205260408082205490921681522060020154600160a01b900460ff165b15613bd4576001600160a01b03808316600090815260038501602052604080822054831680835281832054841683529120600101549091168103613a8a576001600160a01b0381811660009081526003860160205260408082205483168252808220600290810154909316808352912090910154909250600160a01b900460ff16156139eb576001600160a01b0380821660008181526003870160205260408082206002808201805460ff60a01b1990811690915588871685528385208201805482169055825487168552928420018054909216600160a01b1790915591905254169250613bce565b6001600160a01b038082166000908152600386016020526040902060020154811690841603613a2157809250613a218484614645565b506001600160a01b0380831660009081526003850160205260408082205483168083528183206002808201805460ff60a01b19908116909155825487168652938520018054909316600160a01b1790925591829052549091613a85918691166147f8565b613bce565b6001600160a01b03818116600090815260038601602052604080822054831682528082206001015490921680825291902060020154909250600160a01b900460ff1615613b34576001600160a01b0380821660008181526003870160205260408082206002808201805460ff60a01b1990811690915588871685528385208201805482169055825487168552928420018054909216600160a01b1790915591905254169250613bce565b6001600160a01b038082166000908152600386016020526040902060010154811690841603613b6a57809250613b6a84846147f8565b506001600160a01b0380831660009081526003850160205260408082205483168083528183206002808201805460ff60a01b19908116909155825487168652938520018054909316600160a01b1790925591829052549091613bce91869116614645565b506138b8565b505080546001600160a01b03166000908152600390910160205260409020600201805460ff60a01b19169055565b60005b6001600160a01b0382811660009081526003850160205260409020600201541615613c52576001600160a01b03918216600090815260038401602052604090206002015490911690613c05565b50919050565b6001600160a01b03811660009081526020839052604081205490819003613c8157613c816159a3565b6117616001826158e1565b81600003613c9c57613c9c6159a3565b82546001840154818103613cf3576040805180820182528581526020808201868152600085815260028a019092529281209151825591516001918201558601805491613ce78361574f565b91905055505050505050565b60006002860181613d056001856158e1565b815260200190815260200160002060010154905080841015613d2957613d296159a3565b83811015613d78576040805180820182528681526020808201878152600086815260028b019092529281209151825591516001918201558701805491613d6e8361574f565b9190505550613daa565b84600287016000613d8a6001866158e1565b815260200190815260200160002060000160008282546127939190615768565b505050505050565b6000806000613dc0856109e3565b60208101519091506000613dd56009886125fc565b5490506000829003613df05760008094509450505050612e13565b80600003613e075785600094509450505050612e13565b6000613e138284615768565b613e1d8489615b49565b613e279190615b60565b90506000613e3582896158e1565b905060006064828760600151613e4b9190615b49565b613e559190615b60565b9050613e618184615768565b613e6b82846158e1565b975097505050505050509250929050565b6000613e89600984612033565b905081816007016000828254613e9f9190615768565b90915550506040518281526001600160a01b038416907f65da9d8bbc1fc112cfb499f15f17b3bfe4c5070dfca6a3e30f7f5f7f17bd86849060200160405180910390a2505050565b613ef6816136f26009856125fc565b816001600160a01b03167f520e62b73478b262ede881cd483440f8e7b8a4651b0ff9154aeb3510089ddbac8260405161194191815260200190565b6001600160a01b038116613f4757613f476159a3565b613f518282613864565b613f7957604051634b2990ed60e11b81526001600160a01b0382166004820152602401610ab7565b6001600160a01b03818116600090815260038401602052604081206001015490918291161580613fc657506001600160a01b03838116600090815260038601602052604090206002015416155b15613fd257508161403e565b506001600160a01b038083166000908152600385016020526040902060020154165b6001600160a01b038181166000908152600386016020526040902060010154161561403e576001600160a01b03908116600090815260038501602052604090206001015416613ff4565b6001600160a01b038181166000908152600386016020526040902060010154161561408a576001600160a01b0380821660009081526003860160205260409020600101541691506140ad565b6001600160a01b0380821660009081526003860160205260409020600201541691505b6001600160a01b038082166000908152600386016020526040808220548584168352912080546001600160a01b031916919092169081179091558015614186576001600160a01b03808216600090815260038701602052604090206001015481169083160361414e576001600160a01b038181166000908152600387016020526040902060010180546001600160a01b0319169185169190911790556141a0565b6001600160a01b038181166000908152600387016020526040902060020180546001600160a01b0319169185169190911790556141a0565b84546001600160a01b0319166001600160a01b0384161785555b6001600160a01b038281166000818152600388016020526040902060020154600160a01b900460ff16159186161461427b576141dd8684876149ab565b6001600160a01b03858116600090815260038801602052604080822060018082015488861680865284862092830180549288166001600160a01b03199384168117909155865284862080548316821790556002938401805494909301805494909716938216841787559285529284208054909316821790925590549152815460ff60a01b1916600160a01b9182900460ff1615159091021790559193915b801561428b5761428b8685614aa3565b6001600160a01b0383166000908152600387016020526040812080546001600160a01b0319908116825560018083018054909216909155600290910180546001600160a81b031916905587018054916142e383615ca7565b90915550506001600160a01b0383166000908152600387016020526040812060070154600288018054919290916127939084906158e1565b60008082121561432d5761432d6159a3565b5090565b6116ee600182016000615115565b600061434e6002848418615b60565b61176190848416615768565b80516a636f6e736f6c652e6c6f67602083016000808483855afa5050505050565b60008072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b83106143ba5772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6d04ee2d6d415b85acef810000000083106143e6576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc10000831061440457662386f26fc10000830492506010015b6305f5e100831061441c576305f5e100830492506008015b612710831061443057612710830492506004015b60648310614442576064830492506002015b600a83106117645760010192915050565b6001600160a01b03811660009081526005830160205260408120546001840154829161448391612c409190615b49565b6001600160a01b0384166000908152600386016020526040812054919250906144b090612a42908461577b565b90506144c4670de0b6b3a764000082615b60565b95945050505050565b607880546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6001600160a01b038216600090815260058401602052604081208054839290614549908490615768565b9091555050825481908490600090614562908490615768565b9091555050600183015461457b90612c40908390615b49565b6001600160a01b038316600090815260038501602052604081208054909190612c6f9084906157f5565b806000036145b1575050565b81546000036145e4576002820154604051636130835f60e01b81526001600160a01b039091166004820152602401610ab7565b81546145f8670de0b6b3a764000083615b49565b6146029190615b60565b8260010160008282546146159190615768565b90915550505050565b604554610100900460ff16612e5b5760405162461bcd60e51b8152600401610ab790615c5c565b6001600160a01b038082166000818152600385016020526040808220600281018054915482871680865293852060010154959094526001600160a01b0319909116938516938417905592169080156146c8576001600160a01b038181166000908152600387016020526040902080546001600160a01b0319169186169190911790555b6001600160a01b038381166000908152600387016020526040902080546001600160a01b03191691841691821790556147195784546001600160a01b0319166001600160a01b0384161785556147ae565b6001600160a01b03808316600090815260038701602052604090206001015481169085160361477a576001600160a01b038281166000908152600387016020526040902060010180546001600160a01b0319169185169190911790556147ae565b6001600160a01b038281166000908152600387016020526040902060020180546001600160a01b0319169185169190911790555b50506001600160a01b039081166000818152600390940160205260408085206001018054939094166001600160a01b03199384168117909455928452919092208054909216179055565b6001600160a01b038082166000818152600385016020526040808220600181018054915482871680865293852060020154959094526001600160a01b03199091169385169384179055921690801561487b576001600160a01b038181166000908152600387016020526040902080546001600160a01b0319169186169190911790555b6001600160a01b038381166000908152600387016020526040902080546001600160a01b03191691841691821790556148cc5784546001600160a01b0319166001600160a01b038416178555614961565b6001600160a01b03808316600090815260038701602052604090206002015481169085160361492d576001600160a01b038281166000908152600387016020526040902060020180546001600160a01b031916918516919091179055614961565b6001600160a01b038281166000908152600387016020526040902060010180546001600160a01b0319169185169190911790555b50506001600160a01b039081166000818152600390940160205260408085206002018054939094166001600160a01b03199384168117909455928452919092208054909216179055565b6001600160a01b038082166000908152600385016020526040808220548584168352912080546001600160a01b0319169190921690811790915580614a085783546001600160a01b0319166001600160a01b038416178455614a9d565b6001600160a01b038082166000908152600386016020526040902060010154811690831603614a69576001600160a01b038181166000908152600386016020526040902060010180546001600160a01b031916918516919091179055614a9d565b6001600160a01b038181166000908152600386016020526040902060020180546001600160a01b0319169185169190911790555b50505050565b60005b82546001600160a01b03838116911614801590614ae857506001600160a01b0382166000908152600384016020526040902060020154600160a01b900460ff16155b15615044576001600160a01b0380831660008181526003860160205260408082205484168083529120600101549092169003614db2576001600160a01b038181166000908152600386016020526040808220600290810154909316808352912090910154909250600160a01b900460ff1615614bcf576001600160a01b0380831660009081526003860160205260408082206002908101805460ff60a01b1990811690915593851683529120018054909116600160a01b179055614bac8482614645565b6001600160a01b0380821660009081526003860160205260409020600201541691505b6001600160a01b0382811660009081526003860160205260408082206001015490921681522060020154600160a01b900460ff16158015614c4257506001600160a01b038281166000908152600386016020526040808220600290810154909316825290200154600160a01b900460ff16155b15614c7d576001600160a01b03821660009081526003850160205260409020600201805460ff60a01b1916600160a01b17905591508161503e565b6001600160a01b038281166000908152600386016020526040808220600290810154909316825290200154600160a01b900460ff16614d32576001600160a01b0380831660008181526003870160205260408082206001810154909416825281206002908101805460ff60a01b19908116909155929091529091018054909116600160a01b179055614d0f84836147f8565b6001600160a01b0380821660009081526003860160205260409020600201541691505b6001600160a01b0380821660009081526003860160205260408082206002908101805487861685528385208301805460ff600160a01b9384900416151590920260ff60a01b19928316178155825482169092559054909416835291200180549091169055614da08482614645565b83546001600160a01b0316925061503e565b6001600160a01b0381811660009081526003860160205260408082206001015490921680825291902060020154909250600160a01b900460ff1615614e62576001600160a01b0380831660009081526003860160205260408082206002908101805460ff60a01b1990811690915593851683529120018054909116600160a01b179055614e3f84826147f8565b6001600160a01b0380821660009081526003860160205260409020600101541691505b6001600160a01b038281166000908152600386016020526040808220600290810154909316825290200154600160a01b900460ff16158015614ed557506001600160a01b0382811660009081526003860160205260408082206001015490921681522060020154600160a01b900460ff16155b15614f10576001600160a01b03821660009081526003850160205260409020600201805460ff60a01b1916600160a01b17905591508161503e565b6001600160a01b0382811660009081526003860160205260408082206001015490921681522060020154600160a01b900460ff16614fbd576001600160a01b03808316600081815260038701602052604080822060029081018054909516835290822001805460ff60a01b19908116909155919052815416600160a01b179055614f9a8483614645565b6001600160a01b0380821660009081526003860160205260409020600101541691505b6001600160a01b038082166000908152600386016020526040808220600290810180548786168552838520808401805460ff600160a01b9485900416151590930260ff60a01b199384161790558254821690925560019091015490941683529120018054909116905561503084826147f8565b83546001600160a01b031692505b50614aa6565b506001600160a01b03166000908152600390910160205260409020600201805460ff60a01b19169055565b6040518060c00160405280615082615136565b8152602001600081526020016000815260200160008152602001600081526020016000151581525090565b82600281019282156150db579160200282015b828111156150db5782358255916020019190600101906150c0565b5061432d929150615154565b82600481019282156150db579160200282015b828111156150db5782518255916020019190600101906150fa565b50805460008255600302906000526020600020908101906116ee9190615169565b60405180608001604052806004906020820280368337509192915050565b5b8082111561432d5760008155600101615155565b5b8082111561432d5780546001600160a01b0319168155600060018201819055600282015560030161516a565b6020808252825182820181905260009190848201906040850190845b818110156151d75783516001600160a01b0316835292840192918401916001016151b2565b50909695505050505050565b6001600160a01b03811681146116ee57600080fd5b80356124f5816151e3565b60006020828403121561521557600080fd5b8135613274816151e3565b81516101208201908260005b600481101561524b57825182526020928301929091019060010161522c565b50505060208301516080830152604083015160a0830152606083015160c0830152608083015160e083015260a0830151611cbf61010084018215159052565b806040810183101561176457600080fd5b60008060c083850312156152ae57600080fd5b6152b8848461528a565b91508360c0840111156152ca57600080fd5b50926040919091019150565b6000602082840312156152e857600080fd5b5035919050565b600060608284031215613c5257600080fd5b600080600060a0848603121561531657600080fd5b8335925061532785602086016152ef565b9150608084013567ffffffffffffffff81111561534357600080fd5b61534f868287016152ef565b9150509250925092565b6000806040838503121561536c57600080fd5b8235615377816151e3565b946020939093013593505050565b60008083601f84011261539757600080fd5b50813567ffffffffffffffff8111156153af57600080fd5b6020830191508360208260051b8501011115612e1357600080fd5b600080602083850312156153dd57600080fd5b823567ffffffffffffffff8111156153f457600080fd5b61540085828601615385565b90969095509350505050565b6000806040838503121561541f57600080fd5b823561542a816151e3565b9150602083013561543a816151e3565b809150509250929050565b60008060006060848603121561545a57600080fd5b83359250602084013591506040840135615473816151e3565b809150509250925092565b80151581146116ee57600080fd5b6000806040838503121561549f57600080fd5b82356154aa816151e3565b9150602083013561543a8161547e565b600080600080600080600080610120898b0312156154d757600080fd5b883597506154e88a60208b016152ef565b9650608089013567ffffffffffffffff8082111561550557600080fd5b6155118c838d016152ef565b975060a08b0135965060c08b0135955060e08b013594506101008b013591508082111561553d57600080fd5b5061554a8b828c01615385565b999c989b5096995094979396929594505050565b60008083601f84011261557057600080fd5b50813567ffffffffffffffff81111561558857600080fd5b6020830191508360208260071b8501011115612e1357600080fd5b6000806000806000806000806000808a8c036101608112156155c457600080fd5b60808112156155d257600080fd5b508a995060808b013567ffffffffffffffff808211156155f157600080fd5b6155fd8e838f01615385565b909b50995060a08d013591508082111561561657600080fd5b6156228e838f0161555e565b909950975060c08d013591508082111561563b57600080fd5b506156488d828e01615385565b909650945061565b905060e08c016151f8565b925061566b8c6101008d0161528a565b915061567a6101408c016151f8565b90509295989b9194979a5092959850565b61010081016040858337608084604084013760c082018360005b60028110156156c45781548352602090920191600191820191016156a5565b505050949350505050565b600080604083850312156156e257600080fd5b82516156ed8161547e565b602084015190925061543a8161547e565b60808181019083833792915050565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b60006001820161576157615761615739565b5060010190565b8082018082111561176457611764615739565b8082018281126000831280158216821582161715612bdc57612bdc615739565b6040815260006157c2604083016007815266756e7374616b6560c81b602082015260400190565b8281036020840152613274816014815273494e53554646494349454e545f42414c414e434560601b602082015260400190565b8181036000831280158383131683831282161715611cbf57611cbf615739565b60408152600061583c604083016007815266756e7374616b6560c81b602082015260400190565b828103602084015261327481600d81526c5354414b455f544f4f5f4c4f5760981b602082015260400190565b80820260008212600160ff1b8414161561588457615884615739565b818105831482151761176457611764615739565b6040815260056040820152647374616b6560d81b606082015260806020820152600061176460808301600d81526c5354414b455f544f4f5f4c4f5760981b602082015260400190565b8181038181111561176457611764615739565b634e487b7160e01b600052601260045260246000fd5b600082615919576159196158f4565b500690565b6040815260006157c260408301600a815269756e64656c656761746560b01b602082015260400190565b60408152600061597260408301600a815269756e64656c656761746560b01b602082015260400190565b828103602084015261327481601281527144454c45474154494f4e5f544f4f5f4c4f5760701b602082015260400190565b634e487b7160e01b600052600160045260246000fd5b60408152600860408201526764656c656761746560c01b606082015260806020820152600061176460808301601281527144454c45474154494f4e5f544f4f5f4c4f5760701b602082015260400190565b6020808252600e908201526d0929cac82989288be988a9c8ea8960931b604082015260600190565b60008235605e19833603018112615a4857600080fd5b9190910192915050565b6000808335601e19843603018112615a6957600080fd5b83018035915067ffffffffffffffff821115615a8457600080fd5b602001915036819003821315612e1357600080fd5b6000808335601e19843603018112615ab057600080fd5b83018035915067ffffffffffffffff821115615acb57600080fd5b6020019150600681901b3603821315612e1357600080fd5b600060408284031215615af557600080fd5b6040516040810181811067ffffffffffffffff82111715615b2657634e487b7160e01b600052604160045260246000fd5b6040528235615b34816151e3565b81526020928301359281019290925250919050565b808202811582820484141761176457611764615739565b600082615b6f57615b6f6158f4565b500490565b60005b83811015615b8f578181015183820152602001615b77565b50506000910152565b6020815260008251806020840152615bb7816040850160208701615b74565b601f01601f19169190910160400192915050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b858152606060208201526000615c0e606083018688615bcb565b8281036040840152615c21818587615bcb565b98975050505050505050565b60008251615a48818460208701615b74565b600060208284031215615c5157600080fd5b81516132748161547e565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b600081615cb657615cb6615739565b50600019019056fea2646970667358221220ad1a6c9a34c722360776fbfc24b96b9a0167062a335b9a824fcce67bc1a110a964736f6c63430008110033\",\n \"linkReferences\": {},\n \"deployedLinkReferences\": {}\n}\n" +var SystemArtifact string = "{\n \"_format\": \"hh-sol-artifact-1\",\n \"contractName\": \"System\",\n \"sourceName\": \"contracts/child/System.sol\",\n \"abi\": [\n {\n \"inputs\": [],\n \"name\": \"NATIVE_TOKEN_CONTRACT\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"NATIVE_TRANSFER_PRECOMPILE\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"NATIVE_TRANSFER_PRECOMPILE_GAS\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"SYSTEM\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"VALIDATOR_PKCHECK_PRECOMPILE\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"VALIDATOR_PKCHECK_PRECOMPILE_GAS\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n }\n ],\n \"bytecode\": \"0x608060405234801561001057600080fd5b5060f58061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610605a5760003560e01c8063284017f514605f5780633b878c2214608457806351351d5314608c578063947287cf14609957806397e5230d1460ae578063e0563ab11460b7575b600080fd5b606761202081565b6040516001600160a01b0390911681526020015b60405180910390f35b606761101081565b60676002600160a01b0381565b60a161520881565b604051908152602001607b565b60a1620249f081565b60676120308156fea2646970667358221220ea2a2e8352aa8e823689bf6e65bc61f11b85e23cf28c8a87eafb08a08a8fce8c64736f6c63430008110033\",\n \"deployedBytecode\": \"0x6080604052348015600f57600080fd5b5060043610605a5760003560e01c8063284017f514605f5780633b878c2214608457806351351d5314608c578063947287cf14609957806397e5230d1460ae578063e0563ab11460b7575b600080fd5b606761202081565b6040516001600160a01b0390911681526020015b60405180910390f35b606761101081565b60676002600160a01b0381565b60a161520881565b604051908152602001607b565b60a1620249f081565b60676120308156fea2646970667358221220ea2a2e8352aa8e823689bf6e65bc61f11b85e23cf28c8a87eafb08a08a8fce8c64736f6c63430008110033\",\n \"linkReferences\": {},\n \"deployedLinkReferences\": {}\n}\n" +var MRC20Artifact string = "{\n \"_format\": \"hh-sol-artifact-1\",\n \"contractName\": \"MRC20\",\n \"sourceName\": \"contracts/child/MRC20.sol\",\n \"abi\": [\n {\n \"inputs\": [\n {\n \"internalType\": \"string\",\n \"name\": \"only\",\n \"type\": \"string\"\n }\n ],\n \"name\": \"Unauthorized\",\n \"type\": \"error\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"owner\",\n \"type\": \"address\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"spender\",\n \"type\": \"address\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"uint256\",\n \"name\": \"value\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"Approval\",\n \"type\": \"event\"\n },\n {\n \"anonymous\": false,\n \"inputs\": [\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"from\",\n \"type\": \"address\"\n },\n {\n \"indexed\": true,\n \"internalType\": \"address\",\n \"name\": \"to\",\n \"type\": \"address\"\n },\n {\n \"indexed\": false,\n \"internalType\": \"uint256\",\n \"name\": \"value\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"Transfer\",\n \"type\": \"event\"\n },\n {\n \"inputs\": [],\n \"name\": \"NATIVE_TOKEN_CONTRACT\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"NATIVE_TRANSFER_PRECOMPILE\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"NATIVE_TRANSFER_PRECOMPILE_GAS\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"SYSTEM\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"VALIDATOR_PKCHECK_PRECOMPILE\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"VALIDATOR_PKCHECK_PRECOMPILE_GAS\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"owner\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"address\",\n \"name\": \"spender\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"allowance\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"spender\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"amount\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"approve\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"account\",\n \"type\": \"address\"\n }\n ],\n \"name\": \"balanceOf\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"decimals\",\n \"outputs\": [\n {\n \"internalType\": \"uint8\",\n \"name\": \"\",\n \"type\": \"uint8\"\n }\n ],\n \"stateMutability\": \"pure\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"spender\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"subtractedValue\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"decreaseAllowance\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"spender\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"addedValue\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"increaseAllowance\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"predicate_\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"string\",\n \"name\": \"name_\",\n \"type\": \"string\"\n },\n {\n \"internalType\": \"string\",\n \"name\": \"symbol_\",\n \"type\": \"string\"\n }\n ],\n \"name\": \"initialize\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"name\",\n \"outputs\": [\n {\n \"internalType\": \"string\",\n \"name\": \"\",\n \"type\": \"string\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n },\n {\n \"internalType\": \"address\",\n \"name\": \"sender\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"bytes\",\n \"name\": \"data\",\n \"type\": \"bytes\"\n }\n ],\n \"name\": \"onStateReceive\",\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"predicate\",\n \"outputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"\",\n \"type\": \"address\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"symbol\",\n \"outputs\": [\n {\n \"internalType\": \"string\",\n \"name\": \"\",\n \"type\": \"string\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [],\n \"name\": \"totalSupply\",\n \"outputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"\",\n \"type\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"to\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"amount\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"transfer\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"address\",\n \"name\": \"from\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"address\",\n \"name\": \"to\",\n \"type\": \"address\"\n },\n {\n \"internalType\": \"uint256\",\n \"name\": \"amount\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"transferFrom\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n },\n {\n \"inputs\": [\n {\n \"internalType\": \"uint256\",\n \"name\": \"amount\",\n \"type\": \"uint256\"\n }\n ],\n \"name\": \"withdraw\",\n \"outputs\": [\n {\n \"internalType\": \"bool\",\n \"name\": \"\",\n \"type\": \"bool\"\n }\n ],\n \"stateMutability\": \"nonpayable\",\n \"type\": \"function\"\n }\n ],\n \"bytecode\": \"0x608060405234801561001057600080fd5b50611150806100206000396000f3fe608060405234801561001057600080fd5b50600436106101375760003560e01c806370a08231116100b8578063a457c2d71161007c578063a457c2d71461025a578063a9059cbb1461026d578063dd62ed3e14610280578063e0563ab114610293578063e61987051461029c578063eeb49945146102af57600080fd5b806370a082311461020f578063906571471461022a578063947287cf1461023f57806395d89b411461024857806397e5230d1461025057600080fd5b80632e1a7d4d116100ff5780632e1a7d4d146101c3578063313ce567146101d657806339509351146101e55780633b878c22146101f857806351351d531461020157600080fd5b806306fdde031461013c578063095ea7b31461015a57806318160ddd1461017d57806323b872dd1461018f578063284017f5146101a2575b600080fd5b6101446102c2565b6040516101519190610c5b565b60405180910390f35b61016d610168366004610ca6565b610354565b6040519015158152602001610151565b6033545b604051908152602001610151565b61016d61019d366004610cd2565b61036e565b6101ab61202081565b6040516001600160a01b039091168152602001610151565b61016d6101d1366004610d13565b610392565b60405160128152602001610151565b61016d6101f3366004610ca6565b6103a6565b6101ab61101081565b6101ab6002600160a01b0381565b61018161021d366004610d2c565b6001600160a01b03163190565b61023d610238366004610df3565b6103c8565b005b61018161520881565b61014461044b565b610181620249f081565b61016d610268366004610ca6565b61045a565b61016d61027b366004610ca6565b6104d5565b61018161028e366004610e69565b6104e3565b6101ab61203081565b6036546101ab906001600160a01b031681565b61023d6102bd366004610ea2565b61050e565b6060603480546102d190610f2b565b80601f01602080910402602001604051908101604052809291908181526020018280546102fd90610f2b565b801561034a5780601f1061031f5761010080835404028352916020019161034a565b820191906000526020600020905b81548152906001019060200180831161032d57829003601f168201915b5050505050905090565b6000336103628185856105c7565b60019150505b92915050565b60003361037c8582856106eb565b61038785858561075f565b506001949350505050565b600061039e3383610942565b506001919050565b6000336103628185856103b983836104e3565b6103c39190610f7b565b6105c7565b336002600160a01b03146104115760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b60448201526064015b60405180910390fd5b603680546001600160a01b0319166001600160a01b03851617905560346104388382610fd9565b5060356104458282610fd9565b50505050565b6060603580546102d190610f2b565b6000338161046882866104e3565b9050838110156104c85760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608401610408565b61038782868684036105c7565b60003361036281858561075f565b6001600160a01b03918216600090815260326020908152604080832093909416825291909152205490565b61100133146105545760405162461bcd60e51b815260206004820152601260248201527127a7262cafa9aa20aa22a922a1a2a4ab22a960711b6044820152606401610408565b6036546001600160a01b038481169116146105a25760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a2a72222a960911b6044820152606401610408565b6000806105b183850185610ca6565b915091506105bf8282610ac5565b505050505050565b6001600160a01b0383166106295760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610408565b6001600160a01b03821661068a5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610408565b6001600160a01b0383811660008181526032602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b60006106f784846104e3565b9050600019811461044557818110156107525760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610408565b61044584848484036105c7565b6001600160a01b0383166107c35760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610408565b6001600160a01b0382166108255760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610408565b604080516001600160a01b0385811660208301528416918101919091526060810182905260009081906120209060800160408051601f198184030181529082905261086f91611099565b6000604051808303816000865af19150503d80600081146108ac576040519150601f19603f3d011682016040523d82523d6000602084013e6108b1565b606091505b50915091508180156108d25750808060200190518101906108d291906110b5565b6108ee5760405162461bcd60e51b8152600401610408906110d7565b836001600160a01b0316856001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8560405161093391815260200190565b60405180910390a35050505050565b6001600160a01b0382166109a25760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610408565b80603360008282546109b49190611107565b9091555050604080516001600160a01b038416602082015260009181018290526060810183905281906120209060800160408051601f19818403018152908290526109fe91611099565b6000604051808303816000865af19150503d8060008114610a3b576040519150601f19603f3d011682016040523d82523d6000602084013e610a40565b606091505b5091509150818015610a61575080806020019051810190610a6191906110b5565b610a7d5760405162461bcd60e51b8152600401610408906110d7565b6040518381526000906001600160a01b038616907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906020015b60405180910390a350505050565b6001600160a01b038216610b1b5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610408565b8060336000828254610b2d9190610f7b565b9091555050604080516000602082018190526001600160a01b038516928201929092526060810183905281906120209060800160408051601f1981840301815290829052610b7a91611099565b6000604051808303816000865af19150503d8060008114610bb7576040519150601f19603f3d011682016040523d82523d6000602084013e610bbc565b606091505b5091509150818015610bdd575080806020019051810190610bdd91906110b5565b610bf95760405162461bcd60e51b8152600401610408906110d7565b6040518381526001600160a01b038516906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001610ab7565b60005b83811015610c52578181015183820152602001610c3a565b50506000910152565b6020815260008251806020840152610c7a816040850160208701610c37565b601f01601f19169190910160400192915050565b6001600160a01b0381168114610ca357600080fd5b50565b60008060408385031215610cb957600080fd5b8235610cc481610c8e565b946020939093013593505050565b600080600060608486031215610ce757600080fd5b8335610cf281610c8e565b92506020840135610d0281610c8e565b929592945050506040919091013590565b600060208284031215610d2557600080fd5b5035919050565b600060208284031215610d3e57600080fd5b8135610d4981610c8e565b9392505050565b634e487b7160e01b600052604160045260246000fd5b600082601f830112610d7757600080fd5b813567ffffffffffffffff80821115610d9257610d92610d50565b604051601f8301601f19908116603f01168101908282118183101715610dba57610dba610d50565b81604052838152866020858801011115610dd357600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080600060608486031215610e0857600080fd5b8335610e1381610c8e565b9250602084013567ffffffffffffffff80821115610e3057600080fd5b610e3c87838801610d66565b93506040860135915080821115610e5257600080fd5b50610e5f86828701610d66565b9150509250925092565b60008060408385031215610e7c57600080fd5b8235610e8781610c8e565b91506020830135610e9781610c8e565b809150509250929050565b60008060008060608587031215610eb857600080fd5b843593506020850135610eca81610c8e565b9250604085013567ffffffffffffffff80821115610ee757600080fd5b818701915087601f830112610efb57600080fd5b813581811115610f0a57600080fd5b886020828501011115610f1c57600080fd5b95989497505060200194505050565b600181811c90821680610f3f57607f821691505b602082108103610f5f57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b8082018082111561036857610368610f65565b601f821115610fd457600081815260208120601f850160051c81016020861015610fb55750805b601f850160051c820191505b818110156105bf57828155600101610fc1565b505050565b815167ffffffffffffffff811115610ff357610ff3610d50565b611007816110018454610f2b565b84610f8e565b602080601f83116001811461103c57600084156110245750858301515b600019600386901b1c1916600185901b1785556105bf565b600085815260208120601f198616915b8281101561106b5788860151825594840194600190910190840161104c565b50858210156110895787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600082516110ab818460208701610c37565b9190910192915050565b6000602082840312156110c757600080fd5b81518015158114610d4957600080fd5b60208082526016908201527514149150d3d35412531157d0d0531317d1905253115160521b604082015260600190565b8181038181111561036857610368610f6556fea2646970667358221220e6e3b82eab14e5fef3f9762c68c2cf4f007d24bcc6b3b83a3d32fca4ba3a697b64736f6c63430008110033\",\n \"deployedBytecode\": \"0x608060405234801561001057600080fd5b50600436106101375760003560e01c806370a08231116100b8578063a457c2d71161007c578063a457c2d71461025a578063a9059cbb1461026d578063dd62ed3e14610280578063e0563ab114610293578063e61987051461029c578063eeb49945146102af57600080fd5b806370a082311461020f578063906571471461022a578063947287cf1461023f57806395d89b411461024857806397e5230d1461025057600080fd5b80632e1a7d4d116100ff5780632e1a7d4d146101c3578063313ce567146101d657806339509351146101e55780633b878c22146101f857806351351d531461020157600080fd5b806306fdde031461013c578063095ea7b31461015a57806318160ddd1461017d57806323b872dd1461018f578063284017f5146101a2575b600080fd5b6101446102c2565b6040516101519190610c5b565b60405180910390f35b61016d610168366004610ca6565b610354565b6040519015158152602001610151565b6033545b604051908152602001610151565b61016d61019d366004610cd2565b61036e565b6101ab61202081565b6040516001600160a01b039091168152602001610151565b61016d6101d1366004610d13565b610392565b60405160128152602001610151565b61016d6101f3366004610ca6565b6103a6565b6101ab61101081565b6101ab6002600160a01b0381565b61018161021d366004610d2c565b6001600160a01b03163190565b61023d610238366004610df3565b6103c8565b005b61018161520881565b61014461044b565b610181620249f081565b61016d610268366004610ca6565b61045a565b61016d61027b366004610ca6565b6104d5565b61018161028e366004610e69565b6104e3565b6101ab61203081565b6036546101ab906001600160a01b031681565b61023d6102bd366004610ea2565b61050e565b6060603480546102d190610f2b565b80601f01602080910402602001604051908101604052809291908181526020018280546102fd90610f2b565b801561034a5780601f1061031f5761010080835404028352916020019161034a565b820191906000526020600020905b81548152906001019060200180831161032d57829003601f168201915b5050505050905090565b6000336103628185856105c7565b60019150505b92915050565b60003361037c8582856106eb565b61038785858561075f565b506001949350505050565b600061039e3383610942565b506001919050565b6000336103628185856103b983836104e3565b6103c39190610f7b565b6105c7565b336002600160a01b03146104115760405163973d02cb60e01b815260206004820152600a60248201526914d654d5115350d0531360b21b60448201526064015b60405180910390fd5b603680546001600160a01b0319166001600160a01b03851617905560346104388382610fd9565b5060356104458282610fd9565b50505050565b6060603580546102d190610f2b565b6000338161046882866104e3565b9050838110156104c85760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152608401610408565b61038782868684036105c7565b60003361036281858561075f565b6001600160a01b03918216600090815260326020908152604080832093909416825291909152205490565b61100133146105545760405162461bcd60e51b815260206004820152601260248201527127a7262cafa9aa20aa22a922a1a2a4ab22a960711b6044820152606401610408565b6036546001600160a01b038481169116146105a25760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a2a72222a960911b6044820152606401610408565b6000806105b183850185610ca6565b915091506105bf8282610ac5565b505050505050565b6001600160a01b0383166106295760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610408565b6001600160a01b03821661068a5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610408565b6001600160a01b0383811660008181526032602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b60006106f784846104e3565b9050600019811461044557818110156107525760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610408565b61044584848484036105c7565b6001600160a01b0383166107c35760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610408565b6001600160a01b0382166108255760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610408565b604080516001600160a01b0385811660208301528416918101919091526060810182905260009081906120209060800160408051601f198184030181529082905261086f91611099565b6000604051808303816000865af19150503d80600081146108ac576040519150601f19603f3d011682016040523d82523d6000602084013e6108b1565b606091505b50915091508180156108d25750808060200190518101906108d291906110b5565b6108ee5760405162461bcd60e51b8152600401610408906110d7565b836001600160a01b0316856001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8560405161093391815260200190565b60405180910390a35050505050565b6001600160a01b0382166109a25760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b6064820152608401610408565b80603360008282546109b49190611107565b9091555050604080516001600160a01b038416602082015260009181018290526060810183905281906120209060800160408051601f19818403018152908290526109fe91611099565b6000604051808303816000865af19150503d8060008114610a3b576040519150601f19603f3d011682016040523d82523d6000602084013e610a40565b606091505b5091509150818015610a61575080806020019051810190610a6191906110b5565b610a7d5760405162461bcd60e51b8152600401610408906110d7565b6040518381526000906001600160a01b038616907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906020015b60405180910390a350505050565b6001600160a01b038216610b1b5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610408565b8060336000828254610b2d9190610f7b565b9091555050604080516000602082018190526001600160a01b038516928201929092526060810183905281906120209060800160408051601f1981840301815290829052610b7a91611099565b6000604051808303816000865af19150503d8060008114610bb7576040519150601f19603f3d011682016040523d82523d6000602084013e610bbc565b606091505b5091509150818015610bdd575080806020019051810190610bdd91906110b5565b610bf95760405162461bcd60e51b8152600401610408906110d7565b6040518381526001600160a01b038516906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001610ab7565b60005b83811015610c52578181015183820152602001610c3a565b50506000910152565b6020815260008251806020840152610c7a816040850160208701610c37565b601f01601f19169190910160400192915050565b6001600160a01b0381168114610ca357600080fd5b50565b60008060408385031215610cb957600080fd5b8235610cc481610c8e565b946020939093013593505050565b600080600060608486031215610ce757600080fd5b8335610cf281610c8e565b92506020840135610d0281610c8e565b929592945050506040919091013590565b600060208284031215610d2557600080fd5b5035919050565b600060208284031215610d3e57600080fd5b8135610d4981610c8e565b9392505050565b634e487b7160e01b600052604160045260246000fd5b600082601f830112610d7757600080fd5b813567ffffffffffffffff80821115610d9257610d92610d50565b604051601f8301601f19908116603f01168101908282118183101715610dba57610dba610d50565b81604052838152866020858801011115610dd357600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080600060608486031215610e0857600080fd5b8335610e1381610c8e565b9250602084013567ffffffffffffffff80821115610e3057600080fd5b610e3c87838801610d66565b93506040860135915080821115610e5257600080fd5b50610e5f86828701610d66565b9150509250925092565b60008060408385031215610e7c57600080fd5b8235610e8781610c8e565b91506020830135610e9781610c8e565b809150509250929050565b60008060008060608587031215610eb857600080fd5b843593506020850135610eca81610c8e565b9250604085013567ffffffffffffffff80821115610ee757600080fd5b818701915087601f830112610efb57600080fd5b813581811115610f0a57600080fd5b886020828501011115610f1c57600080fd5b95989497505060200194505050565b600181811c90821680610f3f57607f821691505b602082108103610f5f57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b8082018082111561036857610368610f65565b601f821115610fd457600081815260208120601f850160051c81016020861015610fb55750805b601f850160051c820191505b818110156105bf57828155600101610fc1565b505050565b815167ffffffffffffffff811115610ff357610ff3610d50565b611007816110018454610f2b565b84610f8e565b602080601f83116001811461103c57600084156110245750858301515b600019600386901b1c1916600185901b1785556105bf565b600085815260208120601f198616915b8281101561106b5788860151825594840194600190910190840161104c565b50858210156110895787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600082516110ab818460208701610c37565b9190910192915050565b6000602082840312156110c757600080fd5b81518015158114610d4957600080fd5b60208082526016908201527514149150d3d35412531157d0d0531317d1905253115160521b604082015260600190565b8181038181111561036857610368610f6556fea2646970667358221220e6e3b82eab14e5fef3f9762c68c2cf4f007d24bcc6b3b83a3d32fca4ba3a697b64736f6c63430008110033\",\n \"linkReferences\": {},\n \"deployedLinkReferences\": {}\n}\n" diff --git a/consensus/polybft/contractsapi/helper.go b/consensus/polybft/contractsapi/helper.go new file mode 100644 index 0000000000..070718f15c --- /dev/null +++ b/consensus/polybft/contractsapi/helper.go @@ -0,0 +1,36 @@ +package contractsapi + +import ( + "math/big" + + "github.com/0xPolygon/polygon-edge/types" + "github.com/umbracle/ethgo/abi" +) + +// StateTransactionInput is an abstraction for different state transaction inputs +type StateTransactionInput interface { + // EncodeAbi contains logic for encoding arbitrary data into ABI format + EncodeAbi() ([]byte, error) + // DecodeAbi contains logic for decoding given ABI data + DecodeAbi(b []byte) error +} + +// specific case where we need to encode state sync event as a tuple of tuple +var stateSyncABIType = abi.MustNewType( + "tuple(tuple(uint256 id, address sender, address receiver, bytes data))") + +// ToABI converts StateSyncEvent to ABI +func (sse *StateSyncedEvent) EncodeAbi() ([]byte, error) { + return stateSyncABIType.Encode([]interface{}{sse}) +} + +// AddValidatorUptime is an extension (helper) function on a generated Uptime type +// that adds uptime data for given validator to Uptime struct +func (u *Uptime) AddValidatorUptime(address types.Address, count int64) { + u.UptimeData = append(u.UptimeData, &UptimeData{ + Validator: address, + SignedBlocks: big.NewInt(count), + }) +} + +var _ StateTransactionInput = &CommitEpochFunction{} diff --git a/consensus/polybft/contractsapi/init.go b/consensus/polybft/contractsapi/init.go new file mode 100644 index 0000000000..87b3636c49 --- /dev/null +++ b/consensus/polybft/contractsapi/init.go @@ -0,0 +1,106 @@ +package contractsapi + +import ( + "embed" + "path" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi/artifact" +) + +const ( + testContractsDir = "test-contracts" +) + +var ( + // core-contracts smart contracts + CheckpointManager *artifact.Artifact + ExitHelper *artifact.Artifact + L2StateSender *artifact.Artifact + StateSender *artifact.Artifact + StateReceiver *artifact.Artifact + BLS *artifact.Artifact + BLS256 *artifact.Artifact + System *artifact.Artifact + ChildValidatorSet *artifact.Artifact + MRC20 *artifact.Artifact + + // test smart contracts + //go:embed test-contracts/* + testContracts embed.FS + TestL1StateReceiver *artifact.Artifact + TestWriteBlockMetadata *artifact.Artifact +) + +func init() { + var err error + + CheckpointManager, err = artifact.DecodeArtifact([]byte(CheckpointManagerArtifact)) + if err != nil { + panic(err) + } + + ExitHelper, err = artifact.DecodeArtifact([]byte(ExitHelperArtifact)) + if err != nil { + panic(err) + } + + L2StateSender, err = artifact.DecodeArtifact([]byte(L2StateSenderArtifact)) + if err != nil { + panic(err) + } + + BLS, err = artifact.DecodeArtifact([]byte(BLSArtifact)) + if err != nil { + panic(err) + } + + BLS256, err = artifact.DecodeArtifact([]byte(BN256G2Artifact)) + if err != nil { + panic(err) + } + + StateSender, err = artifact.DecodeArtifact([]byte(StateSenderArtifact)) + if err != nil { + panic(err) + } + + StateReceiver, err = artifact.DecodeArtifact([]byte(StateReceiverArtifact)) + if err != nil { + panic(err) + } + + System, err = artifact.DecodeArtifact([]byte(SystemArtifact)) + if err != nil { + panic(err) + } + + MRC20, err = artifact.DecodeArtifact([]byte(MRC20Artifact)) + if err != nil { + panic(err) + } + + ChildValidatorSet, err = artifact.DecodeArtifact([]byte(ChildValidatorSetArtifact)) + if err != nil { + panic(err) + } + + testL1StateReceiverRaw, err := testContracts.ReadFile(path.Join(testContractsDir, "TestL1StateReceiver.json")) + if err != nil { + panic(err) + } + + TestL1StateReceiver, err = artifact.DecodeArtifact(testL1StateReceiverRaw) + if err != nil { + panic(err) + } + + testWriteBlockMetadataRaw, err := testContracts.ReadFile(path.Join(testContractsDir, "TestWriteBlockMetadata.json")) + if err != nil { + panic(err) + } + + TestWriteBlockMetadata, err = artifact.DecodeArtifact(testWriteBlockMetadataRaw) + if err != nil { + panic(err) + } +} diff --git a/consensus/polybft/contractsapi/init_test.go b/consensus/polybft/contractsapi/init_test.go new file mode 100644 index 0000000000..c64d9659c4 --- /dev/null +++ b/consensus/polybft/contractsapi/init_test.go @@ -0,0 +1,29 @@ +package contractsapi + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestArtifactNotEmpty(t *testing.T) { + require.NotEmpty(t, CheckpointManager.Bytecode) + require.NotEmpty(t, CheckpointManager.DeployedBytecode) + require.NotEmpty(t, CheckpointManager.Abi) + + require.NotEmpty(t, ExitHelper.Bytecode) + require.NotEmpty(t, ExitHelper.DeployedBytecode) + require.NotEmpty(t, ExitHelper.Abi) + + require.NotEmpty(t, L2StateSender.Bytecode) + require.NotEmpty(t, L2StateSender.DeployedBytecode) + require.NotEmpty(t, L2StateSender.Abi) + + require.NotEmpty(t, BLS.Bytecode) + require.NotEmpty(t, BLS.DeployedBytecode) + require.NotEmpty(t, BLS.Abi) + + require.NotEmpty(t, BLS256.Bytecode) + require.NotEmpty(t, BLS256.DeployedBytecode) + require.NotEmpty(t, BLS256.Abi) +} diff --git a/consensus/polybft/contractsapi/test-contracts/TestL1StateReceiver.json b/consensus/polybft/contractsapi/test-contracts/TestL1StateReceiver.json new file mode 100644 index 0000000000..f8e62a97d0 --- /dev/null +++ b/consensus/polybft/contractsapi/test-contracts/TestL1StateReceiver.json @@ -0,0 +1,86 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "TestL1StateReceiver", + "sourceName": "test-contracts/TestL1StateReceiver.sol", + "abi": [ + { + "inputs": [], + "name": "addr", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "counter", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "data", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "id", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_id", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_addr", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "onL2StateReceive", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b5061048e806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c806361bc221a1461005c57806373d4a13a14610078578063767800de1461008d578063af640d0f146100b8578063f43cda8b146100c1575b600080fd5b61006560035481565b6040519081526020015b60405180910390f35b6100806100d6565b60405161006f91906101ab565b6001546100a0906001600160a01b031681565b6040516001600160a01b03909116815260200161006f565b61006560005481565b6100d46100cf36600461020f565b610164565b005b600280546100e3906102e8565b80601f016020809104026020016040519081016040528092919081815260200182805461010f906102e8565b801561015c5780601f106101315761010080835404028352916020019161015c565b820191906000526020600020905b81548152906001019060200180831161013f57829003601f168201915b505050505081565b6000839055600180546001600160a01b0319166001600160a01b03841617905560026101908282610371565b50600380549060006101a183610431565b9190505550505050565b600060208083528351808285015260005b818110156101d8578581018301518582016040015282016101bc565b506000604082860101526040601f19601f8301168501019250505092915050565b634e487b7160e01b600052604160045260246000fd5b60008060006060848603121561022457600080fd5b8335925060208401356001600160a01b038116811461024257600080fd5b9150604084013567ffffffffffffffff8082111561025f57600080fd5b818601915086601f83011261027357600080fd5b813581811115610285576102856101f9565b604051601f8201601f19908116603f011681019083821181831017156102ad576102ad6101f9565b816040528281528960208487010111156102c657600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b600181811c908216806102fc57607f821691505b60208210810361031c57634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111561036c57600081815260208120601f850160051c810160208610156103495750805b601f850160051c820191505b8181101561036857828155600101610355565b5050505b505050565b815167ffffffffffffffff81111561038b5761038b6101f9565b61039f8161039984546102e8565b84610322565b602080601f8311600181146103d457600084156103bc5750858301515b600019600386901b1c1916600185901b178555610368565b600085815260208120601f198616915b82811015610403578886015182559484019460019091019084016103e4565b50858210156104215787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60006001820161045157634e487b7160e01b600052601160045260246000fd5b506001019056fea264697066735822122059d4887cdfb5a893df715259d93546c8e85fa890245389c3d2e7e4fb6eb293de64736f6c63430008110033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100575760003560e01c806361bc221a1461005c57806373d4a13a14610078578063767800de1461008d578063af640d0f146100b8578063f43cda8b146100c1575b600080fd5b61006560035481565b6040519081526020015b60405180910390f35b6100806100d6565b60405161006f91906101ab565b6001546100a0906001600160a01b031681565b6040516001600160a01b03909116815260200161006f565b61006560005481565b6100d46100cf36600461020f565b610164565b005b600280546100e3906102e8565b80601f016020809104026020016040519081016040528092919081815260200182805461010f906102e8565b801561015c5780601f106101315761010080835404028352916020019161015c565b820191906000526020600020905b81548152906001019060200180831161013f57829003601f168201915b505050505081565b6000839055600180546001600160a01b0319166001600160a01b03841617905560026101908282610371565b50600380549060006101a183610431565b9190505550505050565b600060208083528351808285015260005b818110156101d8578581018301518582016040015282016101bc565b506000604082860101526040601f19601f8301168501019250505092915050565b634e487b7160e01b600052604160045260246000fd5b60008060006060848603121561022457600080fd5b8335925060208401356001600160a01b038116811461024257600080fd5b9150604084013567ffffffffffffffff8082111561025f57600080fd5b818601915086601f83011261027357600080fd5b813581811115610285576102856101f9565b604051601f8201601f19908116603f011681019083821181831017156102ad576102ad6101f9565b816040528281528960208487010111156102c657600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b600181811c908216806102fc57607f821691505b60208210810361031c57634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111561036c57600081815260208120601f850160051c810160208610156103495750805b601f850160051c820191505b8181101561036857828155600101610355565b5050505b505050565b815167ffffffffffffffff81111561038b5761038b6101f9565b61039f8161039984546102e8565b84610322565b602080601f8311600181146103d457600084156103bc5750858301515b600019600386901b1c1916600185901b178555610368565b600085815260208120601f198616915b82811015610403578886015182559484019460019091019084016103e4565b50858210156104215787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60006001820161045157634e487b7160e01b600052601160045260246000fd5b506001019056fea264697066735822122059d4887cdfb5a893df715259d93546c8e85fa890245389c3d2e7e4fb6eb293de64736f6c63430008110033", + "linkReferences": {}, + "deployedLinkReferences": {} +} \ No newline at end of file diff --git a/consensus/polybft/contractsapi/test-contracts/TestL1StateReceiver.sol b/consensus/polybft/contractsapi/test-contracts/TestL1StateReceiver.sol new file mode 100644 index 0000000000..0e066c3202 --- /dev/null +++ b/consensus/polybft/contractsapi/test-contracts/TestL1StateReceiver.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +contract TestL1StateReceiver { + uint256 public id; + address public addr; + bytes public data; + uint256 public counter; + + function onL2StateReceive( + uint256 _id, + address _addr, + bytes memory _data + ) public { + id = _id; + addr = _addr; + data = _data; + counter++; + } +} diff --git a/consensus/polybft/contractsapi/test-contracts/TestWriteBlockMetadata.json b/consensus/polybft/contractsapi/test-contracts/TestWriteBlockMetadata.json new file mode 100644 index 0000000000..5b92e99cc2 --- /dev/null +++ b/consensus/polybft/contractsapi/test-contracts/TestWriteBlockMetadata.json @@ -0,0 +1,50 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "TestWriteBlockMetadata", + "sourceName": "test-contracts/TestWriteBlockMetadata.sol", + "abi": [ + { + "inputs": [], + "name": "coinbase", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "data", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "init", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b50610187806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063a6ae0aac14610046578063e1c7392a14610076578063f0ba8440146100f6575b600080fd5b600154610059906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100f46000805460018181018355828052447f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56392830155825480820184554390830155825480820184554290830155825480820184554590830155825480820190935546929091019190915580546001600160a01b03191641179055565b005b610109610104366004610138565b610117565b60405190815260200161006d565b6000818154811061012757600080fd5b600091825260209091200154905081565b60006020828403121561014a57600080fd5b503591905056fea26469706673582212200950a847916ef267078b133380353b5117947fac6a936af48086ceed389149f164736f6c63430008110033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100415760003560e01c8063a6ae0aac14610046578063e1c7392a14610076578063f0ba8440146100f6575b600080fd5b600154610059906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100f46000805460018181018355828052447f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56392830155825480820184554390830155825480820184554290830155825480820184554590830155825480820190935546929091019190915580546001600160a01b03191641179055565b005b610109610104366004610138565b610117565b60405190815260200161006d565b6000818154811061012757600080fd5b600091825260209091200154905081565b60006020828403121561014a57600080fd5b503591905056fea26469706673582212200950a847916ef267078b133380353b5117947fac6a936af48086ceed389149f164736f6c63430008110033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/consensus/polybft/contractsapi/test-contracts/TestWriteBlockMetadata.sol b/consensus/polybft/contractsapi/test-contracts/TestWriteBlockMetadata.sol new file mode 100644 index 0000000000..90fe2a2ee3 --- /dev/null +++ b/consensus/polybft/contractsapi/test-contracts/TestWriteBlockMetadata.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +contract TestWriteBlockMetadata { + uint[] public data; + address public coinbase; + // bytes32 public hash; + + function init() external { + data.push(block.difficulty); + data.push(block.number); + data.push(block.timestamp); + data.push(block.gaslimit); + data.push(block.chainid); + coinbase = block.coinbase; + // data.push(block.basefee); + // hash = blockhash(block.number); + } +} \ No newline at end of file diff --git a/consensus/polybft/extra.go b/consensus/polybft/extra.go new file mode 100644 index 0000000000..9c910d2c9e --- /dev/null +++ b/consensus/polybft/extra.go @@ -0,0 +1,626 @@ +package polybft + +import ( + "fmt" + "math/big" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/bitmap" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/crypto" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/umbracle/ethgo/abi" + "github.com/umbracle/fastrlp" +) + +const ( + // ExtraVanity represents a fixed number of extra-data bytes reserved for proposer vanity + ExtraVanity = 32 + + // ExtraSeal represents the fixed number of extra-data bytes reserved for proposer seal + ExtraSeal = 65 +) + +// PolyBFTMixDigest represents a hash of "PolyBFT Mix" to identify whether the block is from PolyBFT consensus engine +var PolyBFTMixDigest = types.StringToHash("adce6e5230abe012342a44e4e9b6d05997d6f015387ae0e59be924afc7ec70c1") + +// Extra defines the structure of the extra field for Istanbul +type Extra struct { + Validators *ValidatorSetDelta + Seal []byte + Parent *Signature + Committed *Signature + Checkpoint *CheckpointData +} + +// MarshalRLPTo defines the marshal function wrapper for Extra +func (i *Extra) MarshalRLPTo(dst []byte) []byte { + ar := &fastrlp.Arena{} + + return i.MarshalRLPWith(ar).MarshalTo(dst) +} + +// MarshalRLPWith defines the marshal function implementation for Extra +func (i *Extra) MarshalRLPWith(ar *fastrlp.Arena) *fastrlp.Value { + vv := ar.NewArray() + + // Validators + if i.Validators == nil { + vv.Set(ar.NewNullArray()) + } else { + vv.Set(i.Validators.MarshalRLPWith(ar)) + } + + // Seal + if len(i.Seal) == 0 { + vv.Set(ar.NewNull()) + } else { + vv.Set(ar.NewBytes(i.Seal)) + } + + // ParentSeal + if i.Parent == nil { + vv.Set(ar.NewNullArray()) + } else { + vv.Set(i.Parent.MarshalRLPWith(ar)) + } + + // CommittedSeal + if i.Committed == nil { + vv.Set(ar.NewNullArray()) + } else { + vv.Set(i.Committed.MarshalRLPWith(ar)) + } + + // Checkpoint + if i.Checkpoint == nil { + vv.Set(ar.NewNullArray()) + } else { + vv.Set(i.Checkpoint.MarshalRLPWith(ar)) + } + + return vv +} + +// UnmarshalRLP defines the unmarshal function wrapper for Extra +func (i *Extra) UnmarshalRLP(input []byte) error { + return fastrlp.UnmarshalRLP(input, i) +} + +// UnmarshalRLPWith defines the unmarshal implementation for Extra +func (i *Extra) UnmarshalRLPWith(v *fastrlp.Value) error { + const expectedElements = 5 + + elems, err := v.GetElems() + if err != nil { + return err + } + + if num := len(elems); num != expectedElements { + return fmt.Errorf("incorrect elements count to decode Extra, expected %d but found %d", expectedElements, num) + } + + // Validators + if elems[0].Elems() > 0 { + i.Validators = &ValidatorSetDelta{} + if err := i.Validators.UnmarshalRLPWith(elems[0]); err != nil { + return err + } + } + + // Seal + if elems[1].Len() > 0 { + if i.Seal, err = elems[1].GetBytes(i.Seal); err != nil { + return err + } + } + + // Parent + if elems[2].Elems() > 0 { + i.Parent = &Signature{} + if err := i.Parent.UnmarshalRLPWith(elems[2]); err != nil { + return err + } + } + + // Committed + if elems[3].Elems() > 0 { + i.Committed = &Signature{} + if err := i.Committed.UnmarshalRLPWith(elems[3]); err != nil { + return err + } + } + + // Checkpoint + if elems[4].Elems() > 0 { + i.Checkpoint = &CheckpointData{} + if err := i.Checkpoint.UnmarshalRLPWith(elems[4]); err != nil { + return err + } + } + + return nil +} + +// ValidateBasic contains extra data basic set of validations +func (i *Extra) ValidateBasic(parentExtra *Extra) error { + return i.Checkpoint.ValidateBasic(parentExtra.Checkpoint) +} + +// Validate contains extra data validation logic +func (i *Extra) Validate(parentExtra *Extra, currentValidators AccountSet, nextValidators AccountSet) error { + if err := i.Checkpoint.Validate(parentExtra.Checkpoint, currentValidators, nextValidators); err != nil { + return err + } + + return nil +} + +// createValidatorSetDelta calculates ValidatorSetDelta based on the provided old and new validator sets +func createValidatorSetDelta(oldValidatorSet, newValidatorSet AccountSet) (*ValidatorSetDelta, error) { + var addedValidators, updatedValidators AccountSet + + oldValidatorSetMap := make(map[types.Address]*ValidatorMetadata) + removedValidators := map[types.Address]int{} + + for i, validator := range oldValidatorSet { + if (validator.Address != types.Address{}) { + removedValidators[validator.Address] = i + oldValidatorSetMap[validator.Address] = validator + } + } + + for _, newValidator := range newValidatorSet { + // Check if the validator is among both old and new validator set + oldValidator, validatorExists := oldValidatorSetMap[newValidator.Address] + if validatorExists { + if !oldValidator.EqualAddressAndBlsKey(newValidator) { + return nil, fmt.Errorf("validator '%s' found in both old and new validator set, but its BLS keys differ", + newValidator.Address.String()) + } + + // If it is, then discard it from removed validators... + delete(removedValidators, newValidator.Address) + + if !oldValidator.Equals(newValidator) { + updatedValidators = append(updatedValidators, newValidator) + } + } else { + // ...otherwise it is added + addedValidators = append(addedValidators, newValidator) + } + } + + removedValsBitmap := bitmap.Bitmap{} + for _, i := range removedValidators { + removedValsBitmap.Set(uint64(i)) + } + + delta := &ValidatorSetDelta{ + Added: addedValidators, + Updated: updatedValidators, + Removed: removedValsBitmap, + } + + return delta, nil +} + +// ValidatorSetDelta holds information about added and removed validators compared to the previous epoch +type ValidatorSetDelta struct { + // Added is the slice of added validators + Added AccountSet + // Updated is the slice of updated valiadtors + Updated AccountSet + // Removed is a bitmap of the validators removed from the set + Removed bitmap.Bitmap +} + +// MarshalRLPWith marshals ValidatorSetDelta to RLP format +func (d *ValidatorSetDelta) MarshalRLPWith(ar *fastrlp.Arena) *fastrlp.Value { + vv := ar.NewArray() + addedValidatorsRaw := ar.NewArray() + updatedValidatorsRaw := ar.NewArray() + + for _, validatorAccount := range d.Added { + addedValidatorsRaw.Set(validatorAccount.MarshalRLPWith(ar)) + } + + for _, validatorAccount := range d.Updated { + updatedValidatorsRaw.Set(validatorAccount.MarshalRLPWith(ar)) + } + + vv.Set(addedValidatorsRaw) // added + vv.Set(updatedValidatorsRaw) // updated + vv.Set(ar.NewCopyBytes(d.Removed)) // removed + + return vv +} + +// UnmarshalRLPWith unmarshals ValidatorSetDelta from RLP format +func (d *ValidatorSetDelta) UnmarshalRLPWith(v *fastrlp.Value) error { + elems, err := v.GetElems() + if err != nil { + return err + } + + if len(elems) == 0 { + return nil + } else if num := len(elems); num != 3 { + return fmt.Errorf("incorrect elements count to decode validator set delta, expected 3 but found %d", num) + } + + // Validators (added) + { + validatorsRaw, err := elems[0].GetElems() + if err != nil { + return fmt.Errorf("array expected for added validators") + } + + d.Added, err = unmarshalValidators(validatorsRaw) + if err != nil { + return err + } + } + + // Validators (updated) + { + validatorsRaw, err := elems[1].GetElems() + if err != nil { + return fmt.Errorf("array expected for updated validators") + } + + d.Updated, err = unmarshalValidators(validatorsRaw) + if err != nil { + return err + } + } + + // Bitmap (removed) + { + dst, err := elems[2].GetBytes(nil) + if err != nil { + return err + } + + d.Removed = bitmap.Bitmap(dst) + } + + return nil +} + +// unmarshalValidators unmarshals RLP encoded validators and returns AccountSet instance +func unmarshalValidators(validatorsRaw []*fastrlp.Value) (AccountSet, error) { + if len(validatorsRaw) == 0 { + return nil, nil + } + + validators := make(AccountSet, len(validatorsRaw)) + + for i, validatorRaw := range validatorsRaw { + acc := &ValidatorMetadata{} + if err := acc.UnmarshalRLPWith(validatorRaw); err != nil { + return nil, err + } + + validators[i] = acc + } + + return validators, nil +} + +// IsEmpty returns indication whether delta is empty (namely added, updated slices and removed bitmap are empty) +func (d *ValidatorSetDelta) IsEmpty() bool { + return len(d.Added) == 0 && + len(d.Updated) == 0 && + d.Removed.Len() == 0 +} + +// Copy creates deep copy of ValidatorSetDelta +func (d *ValidatorSetDelta) Copy() *ValidatorSetDelta { + added := d.Added.Copy() + removed := make([]byte, len(d.Removed)) + copy(removed, d.Removed) + + return &ValidatorSetDelta{Added: added, Removed: removed} +} + +// fmt.Stringer interface implementation +func (d *ValidatorSetDelta) String() string { + return fmt.Sprintf("Added %v Removed %v Updated %v", d.Added, d.Removed, d.Updated) +} + +// Signature represents aggregated signatures of signers accompanied with a bitmap +// (in order to be able to determine identities of each signer) +type Signature struct { + AggregatedSignature []byte + Bitmap []byte +} + +// MarshalRLPWith marshals Signature object into RLP format +func (s *Signature) MarshalRLPWith(ar *fastrlp.Arena) *fastrlp.Value { + committed := ar.NewArray() + if s.AggregatedSignature == nil { + committed.Set(ar.NewNull()) + } else { + committed.Set(ar.NewBytes(s.AggregatedSignature)) + } + + if s.Bitmap == nil { + committed.Set(ar.NewNull()) + } else { + committed.Set(ar.NewBytes(s.Bitmap)) + } + + return committed +} + +// UnmarshalRLPWith unmarshals Signature object from the RLP format +func (s *Signature) UnmarshalRLPWith(v *fastrlp.Value) error { + vals, err := v.GetElems() + if err != nil { + return fmt.Errorf("array type expected for signature struct") + } + + // there should be exactly two elements (aggregated signature and bitmap) + if num := len(vals); num != 2 { + return fmt.Errorf("incorrect elements count to decode Signature, expected 2 but found %d", num) + } + + s.AggregatedSignature, err = vals[0].GetBytes(nil) + if err != nil { + return err + } + + s.Bitmap, err = vals[1].GetBytes(nil) + if err != nil { + return err + } + + return nil +} + +// VerifyCommittedFields is checking for consensus proof in the header +func (s *Signature) VerifyCommittedFields(validators AccountSet, hash types.Hash, logger hclog.Logger) error { + signers, err := validators.GetFilteredValidators(s.Bitmap) + if err != nil { + return err + } + + validatorSet := NewValidatorSet(validators, logger) + if !validatorSet.HasQuorum(signers.GetAddressesAsSet()) { + return fmt.Errorf("quorum not reached") + } + + blsPublicKeys := make([]*bls.PublicKey, len(signers)) + for i, validator := range signers { + blsPublicKeys[i] = validator.BlsKey + } + + // TODO: refactor AggregatedSignature + aggs, err := bls.UnmarshalSignature(s.AggregatedSignature) + if err != nil { + return err + } + + if !aggs.VerifyAggregated(blsPublicKeys, hash[:]) { + return fmt.Errorf("could not verify aggregated signature") + } + + return nil +} + +var checkpointDataABIType = abi.MustNewType(`tuple( + uint256 chainId, + uint256 blockNumber, + bytes32 blockHash, + uint256 blockRound, + uint256 epochNumber, + bytes32 eventRoot, + bytes32 currentValidatorsHash, + bytes32 nextValidatorsHash)`) + +// CheckpointData represents data needed for checkpointing mechanism +type CheckpointData struct { + BlockRound uint64 + EpochNumber uint64 + CurrentValidatorsHash types.Hash + NextValidatorsHash types.Hash + EventRoot types.Hash +} + +// MarshalRLPWith defines the marshal function implementation for CheckpointData +func (c *CheckpointData) MarshalRLPWith(ar *fastrlp.Arena) *fastrlp.Value { + vv := ar.NewArray() + // BlockRound + vv.Set(ar.NewUint(c.BlockRound)) + // EpochNumber + vv.Set(ar.NewUint(c.EpochNumber)) + // CurrentValidatorsHash + vv.Set(ar.NewBytes(c.CurrentValidatorsHash.Bytes())) + // NextValidatorsHash + vv.Set(ar.NewBytes(c.NextValidatorsHash.Bytes())) + // EventRoot + vv.Set(ar.NewBytes(c.EventRoot.Bytes())) + + return vv +} + +// UnmarshalRLPWith unmarshals CheckpointData object from the RLP format +func (c *CheckpointData) UnmarshalRLPWith(v *fastrlp.Value) error { + vals, err := v.GetElems() + if err != nil { + return fmt.Errorf("array type expected for CheckpointData struct") + } + + // there should be exactly 5 elements: + // BlockRound, EpochNumber, CurrentValidatorsHash, NextValidatorsHash, EventRoot + if num := len(vals); num != 5 { + return fmt.Errorf("incorrect elements count to decode CheckpointData, expected 5 but found %d", num) + } + + // BlockRound + c.BlockRound, err = vals[0].GetUint64() + if err != nil { + return err + } + + // EpochNumber + c.EpochNumber, err = vals[1].GetUint64() + if err != nil { + return err + } + + // CurrentValidatorsHash + currentValidatorsHashRaw, err := vals[2].GetBytes(nil) + if err != nil { + return err + } + + c.CurrentValidatorsHash = types.BytesToHash(currentValidatorsHashRaw) + + // NextValidatorsHash + nextValidatorsHashRaw, err := vals[3].GetBytes(nil) + if err != nil { + return err + } + + c.NextValidatorsHash = types.BytesToHash(nextValidatorsHashRaw) + + // EventRoot + eventRootRaw, err := vals[4].GetBytes(nil) + if err != nil { + return err + } + + c.EventRoot = types.BytesToHash(eventRootRaw) + + return nil +} + +// Copy returns deep copy of CheckpointData instance +func (c *CheckpointData) Copy() *CheckpointData { + newCheckpointData := new(CheckpointData) + *newCheckpointData = *c + + return newCheckpointData +} + +// Hash calculates keccak256 hash of the CheckpointData. +// CheckpointData is ABI encoded and then hashed. +func (c *CheckpointData) Hash(chainID uint64, blockNumber uint64, blockHash types.Hash) (types.Hash, error) { + checkpointMap := map[string]interface{}{ + "chainId": new(big.Int).SetUint64(chainID), + "blockNumber": new(big.Int).SetUint64(blockNumber), + "blockHash": blockHash, + "blockRound": new(big.Int).SetUint64(c.BlockRound), + "epochNumber": new(big.Int).SetUint64(c.EpochNumber), + "eventRoot": c.EventRoot, + "currentValidatorsHash": c.CurrentValidatorsHash, + "nextValidatorsHash": c.NextValidatorsHash, + } + + abiEncoded, err := checkpointDataABIType.Encode(checkpointMap) + if err != nil { + return types.ZeroHash, err + } + + return types.BytesToHash(crypto.Keccak256(abiEncoded)), nil +} + +// ValidateBasic encapsulates basic validation logic for checkpoint data. +// It only checks epoch numbers validity and whether validators hashes are non-empty. +func (c *CheckpointData) ValidateBasic(parentCheckpoint *CheckpointData) error { + if c.EpochNumber != parentCheckpoint.EpochNumber && + c.EpochNumber != parentCheckpoint.EpochNumber+1 { + // epoch-beginning block + // epoch number must be incremented by one compared to parent block's checkpoint + return fmt.Errorf("invalid epoch number for epoch-beginning block") + } + + if c.CurrentValidatorsHash == types.ZeroHash { + return fmt.Errorf("current validators hash must not be empty") + } + + if c.NextValidatorsHash == types.ZeroHash { + return fmt.Errorf("next validators hash must not be empty") + } + + return nil +} + +// Validate encapsulates validation logic for checkpoint data +func (c *CheckpointData) Validate(parentCheckpoint *CheckpointData, + currentValidators AccountSet, nextValidators AccountSet) error { + if err := c.ValidateBasic(parentCheckpoint); err != nil { + return err + } + + // check if currentValidatorsHash, present in CheckpointData is correct + currentValidatorsHash, err := currentValidators.Hash() + if err != nil { + return fmt.Errorf("failed to calculate current validators hash: %w", err) + } + + if currentValidatorsHash != c.CurrentValidatorsHash { + return fmt.Errorf("current validators hashes don't match") + } + + // check if nextValidatorsHash, present in CheckpointData is correct + nextValidatorsHash, err := nextValidators.Hash() + if err != nil { + return fmt.Errorf("failed to calculate next validators hash: %w", err) + } + + if nextValidatorsHash != c.NextValidatorsHash { + return fmt.Errorf("next validators hashes don't match") + } + + // epoch ending blocks have validator set transitions + if !currentValidators.Equals(nextValidators) && + c.EpochNumber != parentCheckpoint.EpochNumber { + // epoch ending blocks should have the same epoch number as parent block + // (as they belong to the same epoch) + return fmt.Errorf("epoch number should not change for epoch-ending block") + } + + return nil +} + +// GetIbftExtraClean returns unmarshaled extra field from the passed in header, +// but without signatures for the given header (it only includes signatures for the parent block) +func GetIbftExtraClean(extraRaw []byte) ([]byte, error) { + extra, err := GetIbftExtra(extraRaw) + if err != nil { + return nil, err + } + + ibftExtra := &Extra{ + Parent: extra.Parent, + Validators: extra.Validators, + Checkpoint: extra.Checkpoint, + Seal: []byte{}, + Committed: &Signature{}, + } + + return ibftExtra.MarshalRLPTo(nil), nil +} + +// GetIbftExtra returns the istanbul extra data field from the passed in header +func GetIbftExtra(extraB []byte) (*Extra, error) { + if len(extraB) < ExtraVanity { + return nil, fmt.Errorf("wrong extra size: %d", len(extraB)) + } + + data := extraB[ExtraVanity:] + extra := &Extra{} + + if err := extra.UnmarshalRLP(data); err != nil { + return nil, err + } + + if extra.Validators == nil { + extra.Validators = &ValidatorSetDelta{} + } + + return extra, nil +} diff --git a/consensus/polybft/extra_test.go b/consensus/polybft/extra_test.go new file mode 100644 index 0000000000..20de0b39f0 --- /dev/null +++ b/consensus/polybft/extra_test.go @@ -0,0 +1,782 @@ +package polybft + +import ( + "crypto/rand" + "math/big" + mrand "math/rand" + "testing" + + "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/consensus/polybft/bitmap" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/umbracle/fastrlp" +) + +func TestExtra_Encoding(t *testing.T) { + t.Parallel() + + parentStr := []byte("Here is the parent signature") + committedStr := []byte("Here is the committed signature") + bitmapStr := []byte("Here are the bitmap bytes") + + addedValidators := newTestValidatorsWithAliases([]string{"A", "B", "C"}).getPublicIdentities() + + removedValidators := bitmap.Bitmap{} + removedValidators.Set(2) + + // different extra data for marshall/unmarshall + var cases = []struct { + extra *Extra + }{ + { + &Extra{}, + }, + { + &Extra{ + Validators: &ValidatorSetDelta{}, + Parent: &Signature{}, + Committed: &Signature{}, + }, + }, + { + &Extra{ + Validators: &ValidatorSetDelta{}, + Seal: []byte{3, 4}, + }, + }, + { + &Extra{ + Validators: &ValidatorSetDelta{ + Added: addedValidators, + }, + Parent: &Signature{}, + Committed: &Signature{}, + }, + }, + { + &Extra{ + Validators: &ValidatorSetDelta{ + Removed: removedValidators, + }, + Parent: &Signature{AggregatedSignature: parentStr, Bitmap: bitmapStr}, + Committed: &Signature{}, + }, + }, + { + &Extra{ + Validators: &ValidatorSetDelta{ + Added: addedValidators, + Updated: addedValidators[1:], + Removed: removedValidators, + }, + Parent: &Signature{}, + Committed: &Signature{AggregatedSignature: committedStr, Bitmap: bitmapStr}, + }, + }, + { + &Extra{ + Parent: &Signature{AggregatedSignature: parentStr, Bitmap: bitmapStr}, + Committed: &Signature{AggregatedSignature: committedStr, Bitmap: bitmapStr}, + }, + }, + { + &Extra{ + Parent: &Signature{AggregatedSignature: parentStr, Bitmap: bitmapStr}, + Committed: &Signature{AggregatedSignature: committedStr, Bitmap: bitmapStr}, + Checkpoint: &CheckpointData{ + BlockRound: 0, + EpochNumber: 3, + CurrentValidatorsHash: types.BytesToHash(generateRandomBytes(t)), + NextValidatorsHash: types.BytesToHash(generateRandomBytes(t)), + EventRoot: types.BytesToHash(generateRandomBytes(t)), + }, + }, + }, + } + + for _, c := range cases { + data := c.extra.MarshalRLPTo(nil) + extra := &Extra{} + assert.NoError(t, extra.UnmarshalRLP(data)) + assert.Equal(t, c.extra, extra) + } +} + +func TestExtra_UnmarshalRLPWith_NegativeCases(t *testing.T) { + t.Parallel() + + t.Run("Incorrect RLP marshalled data type", func(t *testing.T) { + t.Parallel() + + extra := &Extra{} + ar := &fastrlp.Arena{} + require.Error(t, extra.UnmarshalRLPWith(ar.NewBool(false))) + }) + + t.Run("Incorrect count of RLP marshalled array elements", func(t *testing.T) { + t.Parallel() + + extra := &Extra{} + ar := &fastrlp.Arena{} + require.ErrorContains(t, extra.UnmarshalRLPWith(ar.NewArray()), "incorrect elements count to decode Extra, expected 5 but found 0") + }) + + t.Run("Incorrect ValidatorSetDelta marshalled", func(t *testing.T) { + t.Parallel() + + extra := &Extra{} + ar := &fastrlp.Arena{} + extraMarshalled := ar.NewArray() + deltaMarshalled := ar.NewArray() + deltaMarshalled.Set(ar.NewBytes([]byte{0x73})) + extraMarshalled.Set(deltaMarshalled) // ValidatorSetDelta + extraMarshalled.Set(ar.NewBytes([]byte{})) // Seal + extraMarshalled.Set(ar.NewBytes([]byte{})) // Parent + extraMarshalled.Set(ar.NewBytes([]byte{})) // Committed + require.Error(t, extra.UnmarshalRLPWith(extraMarshalled)) + }) + + t.Run("Incorrect Seal marshalled", func(t *testing.T) { + t.Parallel() + + extra := &Extra{} + ar := &fastrlp.Arena{} + extraMarshalled := ar.NewArray() + deltaMarshalled := new(ValidatorSetDelta).MarshalRLPWith(ar) + extraMarshalled.Set(deltaMarshalled) // ValidatorSetDelta + extraMarshalled.Set(ar.NewNull()) // Seal + extraMarshalled.Set(ar.NewBytes([]byte{})) // Parent + extraMarshalled.Set(ar.NewBytes([]byte{})) // Committed + require.Error(t, extra.UnmarshalRLPWith(extraMarshalled)) + }) + + t.Run("Incorrect Parent signatures marshalled", func(t *testing.T) { + t.Parallel() + + extra := &Extra{} + ar := &fastrlp.Arena{} + extraMarshalled := ar.NewArray() + deltaMarshalled := new(ValidatorSetDelta).MarshalRLPWith(ar) + extraMarshalled.Set(deltaMarshalled) // ValidatorSetDelta + extraMarshalled.Set(ar.NewBytes([]byte{})) // Seal + // Parent + parentArr := ar.NewArray() + parentArr.Set(ar.NewBytes([]byte{})) + extraMarshalled.Set(parentArr) + extraMarshalled.Set(ar.NewBytes([]byte{})) // Committed + require.Error(t, extra.UnmarshalRLPWith(extraMarshalled)) + }) + + t.Run("Incorrect Committed signatures marshalled", func(t *testing.T) { + t.Parallel() + + extra := &Extra{} + ar := &fastrlp.Arena{} + extraMarshalled := ar.NewArray() + deltaMarshalled := new(ValidatorSetDelta).MarshalRLPWith(ar) + extraMarshalled.Set(deltaMarshalled) // ValidatorSetDelta + extraMarshalled.Set(ar.NewBytes([]byte{})) // Seal + + // Parent + key := wallet.GenerateAccount() + parentSignature := createSignature(t, []*wallet.Account{key}, types.BytesToHash([]byte("This is test hash"))) + extraMarshalled.Set(parentSignature.MarshalRLPWith(ar)) + + // Committed + committedArr := ar.NewArray() + committedArr.Set(ar.NewBytes([]byte{})) + extraMarshalled.Set(committedArr) + require.Error(t, extra.UnmarshalRLPWith(extraMarshalled)) + }) + + t.Run("Incorrect Checkpoint data marshalled", func(t *testing.T) { + t.Parallel() + + ar := &fastrlp.Arena{} + extraMarshalled := ar.NewArray() + deltaMarshalled := new(ValidatorSetDelta).MarshalRLPWith(ar) + extraMarshalled.Set(deltaMarshalled) // ValidatorSetDelta + extraMarshalled.Set(ar.NewBytes([]byte{})) // Seal + + // Parent + key := wallet.GenerateAccount() + parentSignature := createSignature(t, []*wallet.Account{key}, types.BytesToHash(generateRandomBytes(t))) + extraMarshalled.Set(parentSignature.MarshalRLPWith(ar)) + + // Committed + committedSignature := createSignature(t, []*wallet.Account{key}, types.BytesToHash(generateRandomBytes(t))) + extraMarshalled.Set(committedSignature.MarshalRLPWith(ar)) + + // Checkpoint data + checkpointDataArr := ar.NewArray() + checkpointDataArr.Set(ar.NewBytes(generateRandomBytes(t))) + extraMarshalled.Set(checkpointDataArr) + + extra := &Extra{} + require.Error(t, extra.UnmarshalRLPWith(extraMarshalled)) + }) +} + +func TestSignature_VerifyCommittedFields(t *testing.T) { + t.Parallel() + + t.Run("Valid signatures", func(t *testing.T) { + t.Parallel() + + numValidators := 100 + msgHash := types.Hash{0x1} + + vals := newTestValidators(numValidators) + validatorsMetadata := vals.getPublicIdentities() + validatorSet := vals.toValidatorSet() + + var signatures bls.Signatures + bitmap := bitmap.Bitmap{} + signers := make(map[types.Address]struct{}, len(validatorsMetadata)) + + for i, val := range vals.getValidators() { + bitmap.Set(uint64(i)) + + tempSign, err := val.account.Bls.Sign(msgHash[:]) + require.NoError(t, err) + + signatures = append(signatures, tempSign) + aggs, err := signatures.Aggregate().Marshal() + assert.NoError(t, err) + + s := &Signature{ + AggregatedSignature: aggs, + Bitmap: bitmap, + } + + err = s.VerifyCommittedFields(validatorsMetadata, msgHash, hclog.NewNullLogger()) + signers[val.Address()] = struct{}{} + + if !validatorSet.HasQuorum(signers) { + assert.ErrorContains(t, err, "quorum not reached", "failed for %d", i) + } else { + assert.NoError(t, err) + } + } + }) + + t.Run("Invalid bitmap provided", func(t *testing.T) { + t.Parallel() + + validatorSet := newTestValidators(3).getPublicIdentities() + bmp := bitmap.Bitmap{} + + // Make bitmap invalid, by setting some flag larger than length of validator set to 1 + bmp.Set(uint64(validatorSet.Len() + 1)) + s := &Signature{Bitmap: bmp} + + err := s.VerifyCommittedFields(validatorSet, types.Hash{0x1}, hclog.NewNullLogger()) + require.Error(t, err) + }) +} + +func TestSignature_UnmarshalRLPWith_NegativeCases(t *testing.T) { + t.Parallel() + + t.Run("Incorrect RLP marshalled data type", func(t *testing.T) { + t.Parallel() + + ar := &fastrlp.Arena{} + signature := Signature{} + require.ErrorContains(t, signature.UnmarshalRLPWith(ar.NewNull()), "array type expected for signature struct") + }) + + t.Run("Incorrect AggregatedSignature field data type", func(t *testing.T) { + t.Parallel() + + ar := &fastrlp.Arena{} + signature := Signature{} + signatureMarshalled := ar.NewArray() + signatureMarshalled.Set(ar.NewNull()) + signatureMarshalled.Set(ar.NewNull()) + require.ErrorContains(t, signature.UnmarshalRLPWith(signatureMarshalled), "value is not of type bytes") + }) + + t.Run("Incorrect Bitmap field data type", func(t *testing.T) { + ar := &fastrlp.Arena{} + signature := Signature{} + signatureMarshalled := ar.NewArray() + signatureMarshalled.Set(ar.NewBytes([]byte{0x5, 0x90})) + signatureMarshalled.Set(ar.NewNull()) + require.ErrorContains(t, signature.UnmarshalRLPWith(signatureMarshalled), "value is not of type bytes") + }) +} + +func TestExtra_VerifyCommittedFieldsRandom(t *testing.T) { + t.Parallel() + + numValidators := 100 + vals := newTestValidators(numValidators) + msgHash := types.Hash{0x1} + + var signature bls.Signatures + + bitmap := bitmap.Bitmap{} + valIndxsRnd := mrand.Perm(numValidators)[:numValidators*2/3+1] + + accounts := vals.getValidators() + + for _, index := range valIndxsRnd { + bitmap.Set(uint64(index)) + + tempSign, err := accounts[index].account.Bls.Sign(msgHash[:]) + require.NoError(t, err) + + signature = append(signature, tempSign) + } + + aggs, err := signature.Aggregate().Marshal() + require.NoError(t, err) + + s := &Signature{ + AggregatedSignature: aggs, + Bitmap: bitmap, + } + + err = s.VerifyCommittedFields(vals.getPublicIdentities(), msgHash, hclog.NewNullLogger()) + assert.NoError(t, err) +} + +func TestExtra_CreateValidatorSetDelta_Cases(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + oldSet []string + newSet []string + added []string + updated []string + removed []uint64 + }{ + { + "Simple", + []string{"A", "B", "C", "E", "F"}, + []string{"B", "E", "H"}, + []string{"H"}, + []string{"B", "E"}, + []uint64{0, 2, 4}, + }, + } + + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + vals := newTestValidatorsWithAliases([]string{}) + + for _, name := range c.oldSet { + vals.create(name, 1) + } + for _, name := range c.newSet { + vals.create(name, 1) + } + + oldValidatorSet := vals.getPublicIdentities(c.oldSet...) + // update voting power to random value + maxVotingPower := big.NewInt(100) + for _, name := range c.updated { + v := vals.getValidator(name) + vp, err := rand.Int(rand.Reader, maxVotingPower) + require.NoError(t, err) + // make sure generated voting power is different than the original one + v.votingPower += vp.Uint64() + 1 + } + newValidatorSet := vals.getPublicIdentities(c.newSet...) + + delta, err := createValidatorSetDelta(oldValidatorSet, newValidatorSet) + require.NoError(t, err) + + // added validators + require.Len(t, delta.Added, len(c.added)) + for i, name := range c.added { + require.Equal(t, delta.Added[i].Address, vals.getValidator(name).Address()) + } + + // removed validators + for _, i := range c.removed { + require.True(t, delta.Removed.IsSet(i)) + } + + // updated validators + require.Len(t, delta.Updated, len(c.updated)) + for i, name := range c.updated { + require.Equal(t, delta.Updated[i].Address, vals.getValidator(name).Address()) + } + }) + } +} + +func TestExtra_CreateValidatorSetDelta_BlsDiffer(t *testing.T) { + t.Parallel() + + vals := newTestValidatorsWithAliases([]string{"A", "B", "C", "D", "E", "F"}) + oldValidatorSet := vals.getPublicIdentities("A", "B", "C", "D") + + // change the public bls key of 'B' + newValidatorSet := vals.getPublicIdentities("B", "E", "F") + privateKey, err := bls.GenerateBlsKey() + require.NoError(t, err) + + newValidatorSet[0].BlsKey = privateKey.PublicKey() + + _, err = createValidatorSetDelta(oldValidatorSet, newValidatorSet) + require.Error(t, err) +} + +func TestExtra_InitGenesisValidatorsDelta(t *testing.T) { + t.Parallel() + + t.Run("Happy path", func(t *testing.T) { + t.Parallel() + + const validatorsCount = 7 + vals := newTestValidators(validatorsCount) + + polyBftConfig := PolyBFTConfig{InitialValidatorSet: vals.getParamValidators()} + + delta := &ValidatorSetDelta{ + Added: make(AccountSet, validatorsCount), + Removed: bitmap.Bitmap{}, + } + + var i int + for _, validator := range vals.validators { + delta.Added[i] = &ValidatorMetadata{ + Address: types.Address(validator.account.Ecdsa.Address()), + BlsKey: validator.account.Bls.PublicKey(), + VotingPower: new(big.Int).SetUint64(validator.votingPower), + } + i++ + } + + extra := Extra{Validators: delta} + + genesis := &chain.Genesis{ + Config: &chain.Params{Engine: map[string]interface{}{ + "polybft": polyBftConfig, + }}, + ExtraData: append(make([]byte, ExtraVanity), extra.MarshalRLPTo(nil)...), + } + + genesisExtra, err := GetIbftExtra(genesis.ExtraData) + assert.NoError(t, err) + assert.Len(t, genesisExtra.Validators.Added, validatorsCount) + assert.Empty(t, genesisExtra.Validators.Removed) + }) + + t.Run("Invalid Extra data", func(t *testing.T) { + t.Parallel() + + validators := newTestValidators(5) + polyBftConfig := PolyBFTConfig{InitialValidatorSet: validators.getParamValidators()} + + genesis := &chain.Genesis{ + Config: &chain.Params{Engine: map[string]interface{}{ + "polybft": polyBftConfig, + }}, + ExtraData: append(make([]byte, ExtraVanity), []byte{0x2, 0x3}...), + } + + _, err := GetIbftExtra(genesis.ExtraData) + + require.Error(t, err) + }) +} + +func TestValidatorSetDelta_Copy(t *testing.T) { + t.Parallel() + + const ( + originalValidatorsCount = 10 + addedValidatorsCount = 2 + ) + + oldValidatorSet := newTestValidators(originalValidatorsCount).getPublicIdentities() + newValidatorSet := oldValidatorSet[:len(oldValidatorSet)-2] + originalDelta, err := createValidatorSetDelta(oldValidatorSet, newValidatorSet) + require.NoError(t, err) + require.NotNil(t, originalDelta) + require.Empty(t, originalDelta.Added) + + copiedDelta := originalDelta.Copy() + require.NotNil(t, copiedDelta) + require.NotSame(t, originalDelta, copiedDelta) + require.NotEqual(t, originalDelta, copiedDelta) + require.Empty(t, copiedDelta.Added) + require.Equal(t, copiedDelta.Removed.Len(), originalDelta.Removed.Len()) + + newValidators := newTestValidators(addedValidatorsCount).getPublicIdentities() + copiedDelta.Added = append(copiedDelta.Added, newValidators...) + require.Empty(t, originalDelta.Added) + require.Len(t, copiedDelta.Added, addedValidatorsCount) + require.Equal(t, copiedDelta.Removed.Len(), originalDelta.Removed.Len()) +} + +func TestValidatorSetDelta_UnmarshalRLPWith_NegativeCases(t *testing.T) { + t.Parallel() + + t.Run("Incorrect RLP value type provided", func(t *testing.T) { + t.Parallel() + + ar := &fastrlp.Arena{} + delta := &ValidatorSetDelta{} + require.ErrorContains(t, delta.UnmarshalRLPWith(ar.NewNull()), "value is not of type array") + }) + + t.Run("Empty RLP array provided", func(t *testing.T) { + t.Parallel() + + ar := &fastrlp.Arena{} + delta := &ValidatorSetDelta{} + require.NoError(t, delta.UnmarshalRLPWith(ar.NewArray())) + }) + + t.Run("Incorrect RLP array size", func(t *testing.T) { + t.Parallel() + + ar := &fastrlp.Arena{} + deltaMarshalled := ar.NewArray() + deltaMarshalled.Set(ar.NewBytes([]byte{0x59})) + deltaMarshalled.Set(ar.NewBytes([]byte{0x33})) + deltaMarshalled.Set(ar.NewBytes([]byte{0x26})) + deltaMarshalled.Set(ar.NewBytes([]byte{0x74})) + delta := &ValidatorSetDelta{} + require.ErrorContains(t, delta.UnmarshalRLPWith(deltaMarshalled), "incorrect elements count to decode validator set delta, expected 3 but found 4") + }) + + t.Run("Incorrect RLP value type for Added field", func(t *testing.T) { + t.Parallel() + + ar := &fastrlp.Arena{} + deltaMarshalled := ar.NewArray() + deltaMarshalled.Set(ar.NewBytes([]byte{0x59})) + deltaMarshalled.Set(ar.NewBytes([]byte{0x33})) + deltaMarshalled.Set(ar.NewBytes([]byte{0x27})) + delta := &ValidatorSetDelta{} + require.ErrorContains(t, delta.UnmarshalRLPWith(deltaMarshalled), "array expected for added validators") + }) + + t.Run("Incorrect RLP value type for ValidatorMetadata in Added field", func(t *testing.T) { + t.Parallel() + + ar := &fastrlp.Arena{} + deltaMarshalled := ar.NewArray() + addedArray := ar.NewArray() + addedArray.Set(ar.NewNull()) + deltaMarshalled.Set(addedArray) + deltaMarshalled.Set(ar.NewNullArray()) + deltaMarshalled.Set(ar.NewNull()) + delta := &ValidatorSetDelta{} + require.ErrorContains(t, delta.UnmarshalRLPWith(deltaMarshalled), "value is not of type array") + }) + + t.Run("Incorrect RLP value type for Removed field", func(t *testing.T) { + t.Parallel() + + ar := &fastrlp.Arena{} + deltaMarshalled := ar.NewArray() + addedValidators := newTestValidators(3).getPublicIdentities() + addedArray := ar.NewArray() + updatedArray := ar.NewArray() + for _, validator := range addedValidators { + addedArray.Set(validator.MarshalRLPWith(ar)) + } + for _, validator := range addedValidators { + votingPower, err := rand.Int(rand.Reader, big.NewInt(100)) + require.NoError(t, err) + + validator.VotingPower = new(big.Int).Set(votingPower) + updatedArray.Set(validator.MarshalRLPWith(ar)) + } + deltaMarshalled.Set(addedArray) + deltaMarshalled.Set(updatedArray) + deltaMarshalled.Set(ar.NewNull()) + delta := &ValidatorSetDelta{} + require.ErrorContains(t, delta.UnmarshalRLPWith(deltaMarshalled), "value is not of type bytes") + }) + + t.Run("Incorrect RLP value type for Updated field", func(t *testing.T) { + t.Parallel() + + ar := &fastrlp.Arena{} + deltaMarshalled := ar.NewArray() + deltaMarshalled.Set(ar.NewArray()) + deltaMarshalled.Set(ar.NewBytes([]byte{0x33})) + deltaMarshalled.Set(ar.NewNull()) + delta := &ValidatorSetDelta{} + require.ErrorContains(t, delta.UnmarshalRLPWith(deltaMarshalled), "array expected for updated validators") + }) + + t.Run("Incorrect RLP value type for ValidatorMetadata in Updated field", func(t *testing.T) { + t.Parallel() + + ar := &fastrlp.Arena{} + deltaMarshalled := ar.NewArray() + updatedArray := ar.NewArray() + updatedArray.Set(ar.NewNull()) + deltaMarshalled.Set(ar.NewArray()) + deltaMarshalled.Set(updatedArray) + deltaMarshalled.Set(ar.NewNull()) + delta := &ValidatorSetDelta{} + require.ErrorContains(t, delta.UnmarshalRLPWith(deltaMarshalled), "value is not of type array") + }) +} + +func Test_GetIbftExtraClean_Fail(t *testing.T) { + t.Parallel() + + randomBytes := [ExtraVanity]byte{} + _, err := rand.Read(randomBytes[:]) + require.NoError(t, err) + + extra, err := GetIbftExtraClean(append(randomBytes[:], []byte{0x12, 0x6}...)) + require.Error(t, err) + require.Nil(t, extra) +} + +func TestCheckpointData_Hash(t *testing.T) { + const ( + chainID = uint64(1) + blockNumber = uint64(27) + ) + + blockHash := types.BytesToHash(generateRandomBytes(t)) + origCheckpoint := &CheckpointData{ + BlockRound: 0, + EpochNumber: 3, + CurrentValidatorsHash: types.BytesToHash(generateRandomBytes(t)), + NextValidatorsHash: types.BytesToHash(generateRandomBytes(t)), + EventRoot: types.BytesToHash(generateRandomBytes(t)), + } + copyCheckpoint := &CheckpointData{} + *copyCheckpoint = *origCheckpoint + + origHash, err := origCheckpoint.Hash(chainID, blockNumber, blockHash) + require.NoError(t, err) + + copyHash, err := copyCheckpoint.Hash(chainID, blockNumber, blockHash) + require.NoError(t, err) + + require.Equal(t, origHash, copyHash) +} + +func TestCheckpointData_Validate(t *testing.T) { + t.Parallel() + + currentValidators := newTestValidators(5).getPublicIdentities() + nextValidators := newTestValidators(3).getPublicIdentities() + + currentValidatorsHash, err := currentValidators.Hash() + require.NoError(t, err) + + nextValidatorsHash, err := nextValidators.Hash() + require.NoError(t, err) + + cases := []struct { + name string + parentEpochNumber uint64 + epochNumber uint64 + currentValidators AccountSet + nextValidators AccountSet + currentValidatorsHash types.Hash + nextValidatorsHash types.Hash + errString string + }{ + { + name: "Valid (validator set changes)", + parentEpochNumber: 2, + epochNumber: 2, + currentValidators: currentValidators, + nextValidators: nextValidators, + currentValidatorsHash: currentValidatorsHash, + nextValidatorsHash: nextValidatorsHash, + errString: "", + }, + { + name: "Valid (validator set remains the same)", + parentEpochNumber: 2, + epochNumber: 2, + currentValidators: currentValidators, + nextValidators: currentValidators, + currentValidatorsHash: currentValidatorsHash, + nextValidatorsHash: currentValidatorsHash, + errString: "", + }, + { + name: "Invalid (gap in epoch numbers)", + parentEpochNumber: 2, + epochNumber: 6, + errString: "invalid epoch number for epoch-beginning block", + }, + { + name: "Invalid (empty currentValidatorsHash)", + currentValidators: currentValidators, + nextValidators: currentValidators, + errString: "current validators hash must not be empty", + }, + { + name: "Invalid (empty nextValidatorsHash)", + currentValidators: currentValidators, + nextValidators: currentValidators, + currentValidatorsHash: currentValidatorsHash, + errString: "next validators hash must not be empty", + }, + { + name: "Invalid (incorrect currentValidatorsHash)", + currentValidators: currentValidators, + nextValidators: currentValidators, + currentValidatorsHash: nextValidatorsHash, + nextValidatorsHash: nextValidatorsHash, + errString: "current validators hashes don't match", + }, + { + name: "Invalid (incorrect nextValidatorsHash)", + currentValidators: nextValidators, + nextValidators: nextValidators, + currentValidatorsHash: nextValidatorsHash, + nextValidatorsHash: currentValidatorsHash, + errString: "next validators hashes don't match", + }, + { + name: "Invalid (validator set and epoch numbers change)", + parentEpochNumber: 2, + epochNumber: 3, + currentValidators: currentValidators, + nextValidators: nextValidators, + currentValidatorsHash: currentValidatorsHash, + nextValidatorsHash: nextValidatorsHash, + errString: "epoch number should not change for epoch-ending block", + }, + } + + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + checkpoint := &CheckpointData{ + EpochNumber: c.epochNumber, + CurrentValidatorsHash: c.currentValidatorsHash, + NextValidatorsHash: c.nextValidatorsHash, + } + parentCheckpoint := &CheckpointData{EpochNumber: c.parentEpochNumber} + err := checkpoint.Validate(parentCheckpoint, c.currentValidators, c.nextValidators) + + if c.errString != "" { + require.ErrorContains(t, err, c.errString) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/consensus/polybft/fsm.go b/consensus/polybft/fsm.go new file mode 100644 index 0000000000..9b82c13fe0 --- /dev/null +++ b/consensus/polybft/fsm.go @@ -0,0 +1,583 @@ +package polybft + +import ( + "bytes" + "errors" + "fmt" + "math/big" + + "github.com/0xPolygon/go-ibft/messages" + "github.com/0xPolygon/go-ibft/messages/proto" + "github.com/0xPolygon/polygon-edge/consensus/polybft/bitmap" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/state" + "github.com/0xPolygon/polygon-edge/types" + hcf "github.com/hashicorp/go-hclog" +) + +type blockBuilder interface { + Reset() error + WriteTx(*types.Transaction) error + Fill() + Build(func(h *types.Header)) (*types.FullBlock, error) + GetState() *state.Transition + Receipts() []*types.Receipt +} + +var ( + errUptimeTxDoesNotExist = errors.New("uptime transaction is not found in the epoch ending block") + errUptimeTxNotExpected = errors.New("didn't expect uptime transaction in a non epoch ending block") + errUptimeTxOnlyOneUptimeExpected = errors.New("only one uptime transaction is allowed in epoch ending block") +) + +type fsm struct { + // PolyBFT consensus protocol configuration + config *PolyBFTConfig + + // parent block header + parent *types.Header + + // backend implements methods for retrieving data from block chain + backend blockchainBackend + + // polybftBackend implements methods needed from the polybft + polybftBackend polybftBackend + + // validators is the list of validators for this round + validators ValidatorSet + + // proposerSnapshot keeps information about new proposer + proposerSnapshot *ProposerSnapshot + + // blockBuilder is the block builder for proposers + blockBuilder blockBuilder + + // epochNumber denotes current epoch number + epochNumber uint64 + + // uptimeCounter holds info about number of times validators sealed a block (only present if isEndOfEpoch is true) + uptimeCounter *contractsapi.CommitEpochFunction + + // isEndOfEpoch indicates if epoch reached its end + isEndOfEpoch bool + + // isEndOfSprint indicates if sprint reached its end + isEndOfSprint bool + + // proposerCommitmentToRegister is a commitment that is registered via state transaction by proposer + proposerCommitmentToRegister *CommitmentMessageSigned + + // logger instance + logger hcf.Logger + + // target is the block being computed + target *types.FullBlock + + // exitEventRootHash is the calculated root hash for given checkpoint block + exitEventRootHash types.Hash +} + +// BuildProposal builds a proposal for the current round (used if proposer) +func (f *fsm) BuildProposal(currentRound uint64) ([]byte, error) { + parent := f.parent + + extraParent, err := GetIbftExtra(parent.ExtraData) + if err != nil { + return nil, err + } + + // TODO: we will need to revisit once slashing is implemented + extra := &Extra{Parent: extraParent.Committed} + // for non-epoch ending blocks, currentValidatorsHash is the same as the nextValidatorsHash + nextValidators := f.validators.Accounts() + + if err := f.blockBuilder.Reset(); err != nil { + return nil, fmt.Errorf("failed to initialize block builder: %w", err) + } + + if f.isEndOfEpoch { + tx, err := f.createValidatorsUptimeTx() + if err != nil { + return nil, err + } + + if err := f.blockBuilder.WriteTx(tx); err != nil { + return nil, fmt.Errorf("failed to commit validators uptime transaction: %w", err) + } + + nextValidators, err = f.getCurrentValidators(f.blockBuilder.GetState()) + if err != nil { + return nil, err + } + + validatorsDelta, err := createValidatorSetDelta(f.validators.Accounts(), nextValidators) + if err != nil { + return nil, fmt.Errorf("failed to create validator set delta: %w", err) + } + + extra.Validators = validatorsDelta + f.logger.Trace("[FSM Build Proposal]", "Validators Delta", validatorsDelta) + } + + if f.config.IsBridgeEnabled() { + for _, tx := range f.stateTransactions() { + if err := f.blockBuilder.WriteTx(tx); err != nil { + return nil, fmt.Errorf("failed to commit state transaction. Error: %w", err) + } + } + } + + // fill the block with transactions + f.blockBuilder.Fill() + + currentValidatorsHash, err := f.validators.Accounts().Hash() + if err != nil { + return nil, err + } + + nextValidatorsHash, err := nextValidators.Hash() + if err != nil { + return nil, err + } + + extra.Checkpoint = &CheckpointData{ + BlockRound: currentRound, + EpochNumber: f.epochNumber, + CurrentValidatorsHash: currentValidatorsHash, + NextValidatorsHash: nextValidatorsHash, + EventRoot: f.exitEventRootHash, + } + + stateBlock, err := f.blockBuilder.Build(func(h *types.Header) { + h.ExtraData = append(make([]byte, ExtraVanity), extra.MarshalRLPTo(nil)...) + h.MixHash = PolyBFTMixDigest + }) + + if err != nil { + return nil, err + } + + if f.logger.IsDebug() { + checkpointHash, err := extra.Checkpoint.Hash(f.backend.GetChainID(), f.Height(), stateBlock.Block.Hash()) + if err != nil { + return nil, fmt.Errorf("failed to calculate proposal hash: %w", err) + } + + f.logger.Debug("[FSM Build Proposal]", + "txs", len(stateBlock.Block.Transactions), + "proposal hash", checkpointHash.String()) + } + + f.target = stateBlock + + return stateBlock.Block.MarshalRLP(), nil +} + +func (f *fsm) stateTransactions() []*types.Transaction { + var txns []*types.Transaction + + if f.proposerCommitmentToRegister != nil { + // add register commitment transaction + inputData, err := f.proposerCommitmentToRegister.EncodeAbi() + if err != nil { + f.logger.Error("StateTransactions failed to encode input data for state sync commitment registration", "Error", err) + + return nil + } + + txns = append(txns, + createStateTransactionWithData(f.config.StateReceiverAddr, inputData)) + } + + f.logger.Debug("Apply state transaction", "num", len(txns)) + + return txns +} + +// createValidatorsUptimeTx create a StateTransaction, which invokes ValidatorSet smart contract +// and sends all the necessary metadata to it. +func (f *fsm) createValidatorsUptimeTx() (*types.Transaction, error) { + input, err := f.uptimeCounter.EncodeAbi() + if err != nil { + return nil, err + } + + return createStateTransactionWithData(f.config.ValidatorSetAddr, input), nil +} + +// ValidateCommit is used to validate that a given commit is valid +func (f *fsm) ValidateCommit(signer []byte, seal []byte, proposalHash []byte) error { + from := types.BytesToAddress(signer) + + validator := f.validators.Accounts().GetValidatorMetadata(from) + if validator == nil { + return fmt.Errorf("unable to resolve validator %s", from) + } + + signature, err := bls.UnmarshalSignature(seal) + if err != nil { + return fmt.Errorf("failed to unmarshall signature: %w", err) + } + + if !signature.Verify(validator.BlsKey, proposalHash) { + return fmt.Errorf("incorrect commit signature from %s", from) + } + + return nil +} + +// Validate validates a raw proposal (used if non-proposer) +func (f *fsm) Validate(proposal []byte) error { + var block types.Block + if err := block.UnmarshalRLP(proposal); err != nil { + return fmt.Errorf("failed to validate, cannot decode block data. Error: %w", err) + } + + // validate header fields + if err := validateHeaderFields(f.parent, block.Header); err != nil { + return fmt.Errorf( + "failed to validate header (parent header# %d, current header#%d): %w", + f.parent.Number, + block.Number(), + err, + ) + } + + currentExtra, err := GetIbftExtra(block.Header.ExtraData) + if err != nil { + return fmt.Errorf("cannot get extra data:%w", err) + } + + parentExtra, err := GetIbftExtra(f.parent.ExtraData) + if err != nil { + return err + } + + if err := f.VerifyStateTransactions(block.Transactions); err != nil { + return err + } + + currentValidators := f.validators.Accounts() + nextValidators := f.validators.Accounts() + + validateExtraData := func(transition *state.Transition) error { + nextValidators, err = f.getCurrentValidators(transition) + if err != nil { + return err + } + + if err := currentExtra.Validate(parentExtra, currentValidators, nextValidators); err != nil { + return err + } + + return nil + } + + // TODO: Validate validator set delta? + + // TODO: Move signature validation logic to Extra + blockNumber := block.Number() + if blockNumber > 1 { + // verify parent signature + // We skip block 0 (genesis) and block 1 (parent is genesis) + // since those blocks do not include any parent information with signatures + validators, err := f.polybftBackend.GetValidators(blockNumber-2, nil) + if err != nil { + return fmt.Errorf("cannot get validators:%w", err) + } + + f.logger.Trace("[FSM Validate]", "Block", blockNumber, "parent validators", validators) + + parentCheckpointHash, err := parentExtra.Checkpoint.Hash(f.backend.GetChainID(), f.parent.Number, f.parent.Hash) + if err != nil { + return fmt.Errorf("failed to calculate parent proposal hash: %w", err) + } + + if err := currentExtra.Parent.VerifyCommittedFields(validators, parentCheckpointHash, f.logger); err != nil { + return fmt.Errorf( + "failed to verify signatures for (parent) block#%d, parent signed hash: %v, current block#%d: %w", + f.parent.Number, + parentCheckpointHash, + blockNumber, + err, + ) + } + } + + stateBlock, err := f.backend.ProcessBlock(f.parent, &block, validateExtraData) + if err != nil { + return err + } + + if f.logger.IsDebug() { + checkpointHash, err := currentExtra.Checkpoint.Hash(f.backend.GetChainID(), block.Number(), block.Hash()) + if err != nil { + return fmt.Errorf("failed to calculate proposal hash: %w", err) + } + + f.logger.Debug("[FSM Validate]", "txs", len(block.Transactions), "proposal hash", checkpointHash) + } + + f.target = stateBlock + + return nil +} + +// ValidateSender validates sender address and signature +func (f *fsm) ValidateSender(msg *proto.Message) error { + msgNoSig, err := msg.PayloadNoSig() + if err != nil { + return err + } + + signerAddress, err := wallet.RecoverAddressFromSignature(msg.Signature, msgNoSig) + if err != nil { + return fmt.Errorf("failed to recover address from signature: %w", err) + } + + // verify the signature came from the sender + if !bytes.Equal(msg.From, signerAddress.Bytes()) { + return fmt.Errorf("signer address %s doesn't match From field", signerAddress.String()) + } + + // verify the sender is in the active validator set + if !f.validators.Includes(signerAddress) { + return fmt.Errorf("signer address %s is not included in validator set", signerAddress.String()) + } + + return nil +} + +func (f *fsm) VerifyStateTransactions(transactions []*types.Transaction) error { + var ( + commitmentMessageSignedExists bool + uptimeTransactionExists bool + ) + + for _, tx := range transactions { + if tx.Type != types.StateTx { + continue + } + + decodedStateTx, err := decodeStateTransaction(tx.Input) // used to be Data + if err != nil { + return fmt.Errorf("unknown state transaction: tx = %v, err = %w", tx.Hash, err) + } + + switch stateTxData := decodedStateTx.(type) { + case *CommitmentMessageSigned: + if !f.isEndOfSprint { + return fmt.Errorf("found commitment tx in block which should not contain it: tx = %v", tx.Hash) + } + + if commitmentMessageSignedExists { + return fmt.Errorf("only one commitment tx is allowed per block: %v", tx.Hash) + } + + commitmentMessageSignedExists = true + signers, err := f.validators.Accounts().GetFilteredValidators(stateTxData.AggSignature.Bitmap) + + if err != nil { + return fmt.Errorf("error for state transaction while retrieving signers: tx = %v, error = %w", tx.Hash, err) + } + + if !f.validators.HasQuorum(signers.GetAddressesAsSet()) { + return fmt.Errorf("quorum size not reached for state tx: %v", tx.Hash) + } + + aggs, err := bls.UnmarshalSignature(stateTxData.AggSignature.AggregatedSignature) + if err != nil { + return fmt.Errorf("error for state transaction while unmarshaling signature: tx = %v, error = %w", tx.Hash, err) + } + + hash, err := stateTxData.Hash() + if err != nil { + return err + } + + verified := aggs.VerifyAggregated(signers.GetBlsKeys(), hash.Bytes()) + if !verified { + return fmt.Errorf("invalid signature for tx = %v", tx.Hash) + } + case *contractsapi.CommitEpochFunction: + if uptimeTransactionExists { + // if we already validated uptime transaction, + //that means someone added two or more uptime tx to block, + // which is not allowed + return errUptimeTxOnlyOneUptimeExpected + } + + uptimeTransactionExists = true + + if err := f.verifyValidatorsUptimeTx(tx); err != nil { + return fmt.Errorf("error while verifying uptime transaction. error: %w", err) + } + } + } + + if f.isEndOfEpoch && !uptimeTransactionExists { + // this is a check if uptime transaction is not in the list of transactions at all + // but it should be + return errUptimeTxDoesNotExist + } + + return nil +} + +// Insert inserts the sealed proposal +func (f *fsm) Insert(proposal []byte, committedSeals []*messages.CommittedSeal) (*types.FullBlock, error) { + newBlock := f.target + + // In this function we should try to return little to no errors since + // at this point everything we have to do is just commit something that + // we should have already computed beforehand. + extra, _ := GetIbftExtra(newBlock.Block.Header.ExtraData) + + // create map for faster access to indexes + nodeIDIndexMap := make(map[types.Address]int, f.validators.Len()) + for i, addr := range f.validators.Accounts().GetAddresses() { + nodeIDIndexMap[addr] = i + } + + // populated bitmap according to nodeId from validator set and committed seals + // also populate slice of signatures + bitmap := bitmap.Bitmap{} + signatures := make(bls.Signatures, 0, len(committedSeals)) + + for _, commSeal := range committedSeals { + signerAddr := types.BytesToAddress(commSeal.Signer) + + index, exists := nodeIDIndexMap[signerAddr] + if !exists { + return nil, fmt.Errorf("invalid node id = %s", signerAddr.String()) + } + + s, err := bls.UnmarshalSignature(commSeal.Signature) + if err != nil { + return nil, fmt.Errorf("invalid signature = %s", commSeal.Signature) + } + + signatures = append(signatures, s) + + bitmap.Set(uint64(index)) + } + + aggregatedSignature, err := signatures.Aggregate().Marshal() + if err != nil { + return nil, fmt.Errorf("could not aggregate seals: %w", err) + } + + // include aggregated signature of all committed seals + // also includes bitmap which contains all indexes from validator set which provides there seals + extra.Committed = &Signature{ + AggregatedSignature: aggregatedSignature, + Bitmap: bitmap, + } + + // Write extra data to header + newBlock.Block.Header.ExtraData = append(make([]byte, ExtraVanity), extra.MarshalRLPTo(nil)...) + + if err := f.backend.CommitBlock(newBlock); err != nil { + return nil, err + } + + return newBlock, nil +} + +// Height returns the height for the current round +func (f *fsm) Height() uint64 { + return f.parent.Number + 1 +} + +// ValidatorSet returns the validator set for the current round +func (f *fsm) ValidatorSet() ValidatorSet { + return f.validators +} + +// getCurrentValidators queries smart contract on the given block height and returns currently active validator set +func (f *fsm) getCurrentValidators(pendingBlockState *state.Transition) (AccountSet, error) { + provider := f.backend.GetStateProvider(pendingBlockState) + systemState := f.backend.GetSystemState(f.config, provider) + newValidators, err := systemState.GetValidatorSet() + + if err != nil { + return nil, fmt.Errorf("failed to retrieve validator set for current block: %w", err) + } + + if f.logger.IsDebug() { + f.logger.Debug("getCurrentValidators", "Validator set", newValidators.String()) + } + + return newValidators, nil +} + +// verifyValidatorsUptimeTx creates uptime transaction and compares its hash with the one extracted from the block. +func (f *fsm) verifyValidatorsUptimeTx(blockUptimeTx *types.Transaction) error { + if f.isEndOfEpoch { + createdUptimeTx, err := f.createValidatorsUptimeTx() + if err != nil { + return err + } + + if blockUptimeTx.Hash != createdUptimeTx.Hash { + return fmt.Errorf( + "invalid uptime transaction. Expected '%s', but got '%s' uptime transaction hash", + blockUptimeTx.Hash, + createdUptimeTx.Hash, + ) + } + + return nil + } + + return errUptimeTxNotExpected +} + +func validateHeaderFields(parent *types.Header, header *types.Header) error { + // verify parent hash + if parent.Hash != header.ParentHash { + return fmt.Errorf("incorrect header parent hash (parent=%s, header parent=%s)", parent.Hash, header.ParentHash) + } + // verify parent number + if header.Number != parent.Number+1 { + return fmt.Errorf("invalid number") + } + // verify time has passed + if header.Timestamp <= parent.Timestamp { + return fmt.Errorf("timestamp older than parent") + } + // verify mix digest + if header.MixHash != PolyBFTMixDigest { + return fmt.Errorf("mix digest is not correct") + } + // difficulty must be > 0 + if header.Difficulty <= 0 { + return fmt.Errorf("difficulty should be greater than zero") + } + // calculated header hash must be correct + if header.Hash != types.HeaderHash(header) { + return fmt.Errorf("invalid header hash") + } + + return nil +} + +// createStateTransactionWithData creates a state transaction +// with provided target address and inputData parameter which is ABI encoded byte array. +func createStateTransactionWithData(target types.Address, inputData []byte) *types.Transaction { + tx := &types.Transaction{ + From: contracts.SystemCaller, + To: &target, + Type: types.StateTx, + Input: inputData, + Gas: types.StateTransactionGasLimit, + GasPrice: big.NewInt(0), + } + + tx.ComputeHash() + + return tx +} diff --git a/consensus/polybft/fsm_test.go b/consensus/polybft/fsm_test.go new file mode 100644 index 0000000000..a1b7518729 --- /dev/null +++ b/consensus/polybft/fsm_test.go @@ -0,0 +1,1411 @@ +package polybft + +import ( + "errors" + "fmt" + "math/big" + "math/rand" + "testing" + "time" + + "github.com/0xPolygon/go-ibft/messages" + "github.com/0xPolygon/go-ibft/messages/proto" + "github.com/0xPolygon/polygon-edge/consensus" + "github.com/0xPolygon/polygon-edge/consensus/polybft/bitmap" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/state" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestFSM_ValidateHeader(t *testing.T) { + t.Parallel() + + parent := &types.Header{Number: 0, Hash: types.BytesToHash([]byte{1, 2, 3})} + header := &types.Header{Number: 0} + + // parent hash + require.ErrorContains(t, validateHeaderFields(parent, header), "incorrect header parent hash") + header.ParentHash = parent.Hash + + // sequence number + require.ErrorContains(t, validateHeaderFields(parent, header), "invalid number") + header.Number = 1 + + // failed timestamp + require.ErrorContains(t, validateHeaderFields(parent, header), "timestamp older than parent") + header.Timestamp = 10 + + // mix digest + require.ErrorContains(t, validateHeaderFields(parent, header), "mix digest is not correct") + header.MixHash = PolyBFTMixDigest + + // difficulty + header.Difficulty = 0 + require.ErrorContains(t, validateHeaderFields(parent, header), "difficulty should be greater than zero") + + header.Difficulty = 1 + header.Hash = types.BytesToHash([]byte{11, 22, 33}) + require.ErrorContains(t, validateHeaderFields(parent, header), "invalid header hash") + + header.ComputeHash() + require.NoError(t, validateHeaderFields(parent, header)) +} + +func TestFSM_verifyValidatorsUptimeTx(t *testing.T) { + t.Parallel() + + fsm := &fsm{ + config: &PolyBFTConfig{ValidatorSetAddr: contracts.ValidatorSetContract}, + isEndOfEpoch: true, + uptimeCounter: createTestUptimeCounter(t, nil, 10), + } + + // include uptime transaction to the epoch ending block + uptimeTx, err := fsm.createValidatorsUptimeTx() + assert.NoError(t, err) + assert.NotNil(t, uptimeTx) + + assert.NoError(t, fsm.verifyValidatorsUptimeTx(uptimeTx)) + + // submit tampered validators uptime transaction to the epoch ending block + alteredUptimeTx := &types.Transaction{ + To: &fsm.config.ValidatorSetAddr, + Input: []byte{}, + Gas: 0, + Type: types.StateTx, + } + assert.ErrorContains(t, fsm.verifyValidatorsUptimeTx(alteredUptimeTx), "invalid uptime transaction") + + // submit validators uptime transaction to the non-epoch ending block + fsm.isEndOfEpoch = false + uptimeTx, err = fsm.createValidatorsUptimeTx() + assert.NoError(t, err) + assert.NotNil(t, uptimeTx) + assert.ErrorContains(t, fsm.verifyValidatorsUptimeTx(uptimeTx), errUptimeTxNotExpected.Error()) +} + +func TestFSM_BuildProposal_WithoutUptimeTxGood(t *testing.T) { + t.Parallel() + + const ( + accountCount = 5 + committedCount = 4 + parentCount = 3 + confirmedStateSyncsCount = 5 + parentBlockNumber = 1023 + currentRound = 1 + ) + + eventRoot := types.ZeroHash + + validators := newTestValidators(accountCount) + validatorSet := validators.getPublicIdentities() + extra := createTestExtra(validatorSet, AccountSet{}, accountCount-1, committedCount, parentCount) + + parent := &types.Header{Number: parentBlockNumber, ExtraData: extra} + parent.ComputeHash() + stateBlock := createDummyStateBlock(parentBlockNumber+1, parent.Hash, extra) + mBlockBuilder := newBlockBuilderMock(stateBlock) + + blockchainMock := &blockchainMock{} + runtime := &consensusRuntime{ + logger: hclog.NewNullLogger(), + config: &runtimeConfig{ + Key: wallet.NewKey(validators.getPrivateIdentities()[0]), + blockchain: blockchainMock, + }, + } + + fsm := &fsm{parent: parent, blockBuilder: mBlockBuilder, config: &PolyBFTConfig{}, backend: blockchainMock, + validators: validators.toValidatorSet(), exitEventRootHash: eventRoot, logger: hclog.NewNullLogger()} + + proposal, err := fsm.BuildProposal(currentRound) + assert.NoError(t, err) + assert.NotNil(t, proposal) + + currentValidatorsHash, err := validatorSet.Hash() + require.NoError(t, err) + + rlpBlock := stateBlock.Block.MarshalRLP() + assert.Equal(t, rlpBlock, proposal) + + block := types.Block{} + require.NoError(t, block.UnmarshalRLP(proposal)) + + checkpoint := &CheckpointData{ + BlockRound: currentRound, + EpochNumber: fsm.epochNumber, + EventRoot: eventRoot, + CurrentValidatorsHash: currentValidatorsHash, + NextValidatorsHash: currentValidatorsHash, + } + + checkpointHash, err := checkpoint.Hash(fsm.backend.GetChainID(), block.Number(), block.Hash()) + require.NoError(t, err) + + msg := runtime.BuildPrePrepareMessage(proposal, nil, &proto.View{}) + require.Equal(t, checkpointHash.Bytes(), msg.GetPreprepareData().ProposalHash) + + mBlockBuilder.AssertExpectations(t) +} + +func TestFSM_BuildProposal_WithUptimeTxGood(t *testing.T) { + t.Parallel() + + const ( + accountCount = 5 + committedCount = 4 + parentCount = 3 + confirmedStateSyncsCount = 5 + currentRound = 0 + parentBlockNumber = 1023 + ) + + eventRoot := types.ZeroHash + + validators := newTestValidators(accountCount) + extra := createTestExtra(validators.getPublicIdentities(), AccountSet{}, accountCount-1, committedCount, parentCount) + + parent := &types.Header{Number: parentBlockNumber, ExtraData: extra} + parent.ComputeHash() + stateBlock := createDummyStateBlock(parentBlockNumber+1, parent.Hash, extra) + + transition := &state.Transition{} + mBlockBuilder := newBlockBuilderMock(stateBlock) + mBlockBuilder.On("WriteTx", mock.Anything).Return(error(nil)).Once() + mBlockBuilder.On("GetState").Return(transition).Once() + + systemStateMock := new(systemStateMock) + systemStateMock.On("GetValidatorSet").Return(nil).Once() + + blockChainMock := new(blockchainMock) + blockChainMock.On("GetStateProvider", mock.Anything). + Return(NewStateProvider(transition)).Once() + blockChainMock.On("GetSystemState", mock.Anything, mock.Anything).Return(systemStateMock).Once() + + runtime := &consensusRuntime{ + logger: hclog.NewNullLogger(), + config: &runtimeConfig{ + Key: wallet.NewKey(validators.getPrivateIdentities()[0]), + blockchain: blockChainMock, + }, + } + + fsm := &fsm{parent: parent, blockBuilder: mBlockBuilder, config: &PolyBFTConfig{}, backend: blockChainMock, + isEndOfEpoch: true, + validators: validators.toValidatorSet(), + uptimeCounter: createTestUptimeCounter(t, nil, 10), + exitEventRootHash: eventRoot, + logger: hclog.NewNullLogger(), + } + + proposal, err := fsm.BuildProposal(currentRound) + assert.NoError(t, err) + assert.NotNil(t, proposal) + + block := types.Block{} + require.NoError(t, block.UnmarshalRLP(proposal)) + + assert.Equal(t, stateBlock.Block.MarshalRLP(), proposal) + + currentValidatorsHash, err := validators.getPublicIdentities().Hash() + require.NoError(t, err) + + nextValidatorsHash, err := AccountSet{}.Hash() + require.NoError(t, err) + + checkpoint := &CheckpointData{ + BlockRound: currentRound, + EpochNumber: fsm.epochNumber, + EventRoot: eventRoot, + CurrentValidatorsHash: currentValidatorsHash, + NextValidatorsHash: nextValidatorsHash, + } + + checkpointHash, err := checkpoint.Hash(fsm.backend.GetChainID(), block.Number(), block.Hash()) + require.NoError(t, err) + + msg := runtime.BuildPrePrepareMessage(proposal, nil, &proto.View{}) + require.Equal(t, checkpointHash.Bytes(), msg.GetPreprepareData().ProposalHash) + + mBlockBuilder.AssertExpectations(t) + systemStateMock.AssertExpectations(t) + blockChainMock.AssertExpectations(t) +} + +func TestFSM_BuildProposal_EpochEndingBlock_FailedToCommitStateTx(t *testing.T) { + t.Parallel() + + const ( + accountCount = 5 + committedCount = 4 + parentCount = 3 + parentBlockNumber = 1023 + ) + + validators := newTestValidators(accountCount) + extra := createTestExtra(validators.getPublicIdentities(), AccountSet{}, accountCount-1, committedCount, parentCount) + + parent := &types.Header{Number: parentBlockNumber, ExtraData: extra} + + mBlockBuilder := new(blockBuilderMock) + mBlockBuilder.On("WriteTx", mock.Anything).Return(errors.New("error")).Once() + mBlockBuilder.On("Reset").Return(error(nil)).Once() + + validatorSet := NewValidatorSet(validators.getPublicIdentities(), hclog.NewNullLogger()) + + fsm := &fsm{parent: parent, blockBuilder: mBlockBuilder, config: &PolyBFTConfig{}, backend: &blockchainMock{}, + isEndOfEpoch: true, + validators: validatorSet, + uptimeCounter: createTestUptimeCounter(t, nil, 10), + exitEventRootHash: types.ZeroHash, + } + + _, err := fsm.BuildProposal(0) + assert.ErrorContains(t, err, "failed to commit validators uptime transaction") + mBlockBuilder.AssertExpectations(t) +} + +func TestFSM_BuildProposal_EpochEndingBlock_ValidatorsDeltaExists(t *testing.T) { + t.Parallel() + + const ( + validatorsCount = 6 + remainingValidatorsCount = 3 + signaturesCount = 4 + parentBlockNumber = 49 + ) + + validators := newTestValidators(validatorsCount).getPublicIdentities() + extra := createTestExtra(validators, AccountSet{}, validatorsCount-1, signaturesCount, signaturesCount) + parent := &types.Header{Number: parentBlockNumber, ExtraData: extra} + parent.ComputeHash() + stateBlock := createDummyStateBlock(parentBlockNumber+1, parent.Hash, extra) + + transition := &state.Transition{} + blockBuilderMock := newBlockBuilderMock(stateBlock) + blockBuilderMock.On("WriteTx", mock.Anything).Return(error(nil)).Once() + blockBuilderMock.On("GetState").Return(transition).Once() + + newValidators := validators[:remainingValidatorsCount].Copy() + addedValidators := newTestValidators(2).getPublicIdentities() + newValidators = append(newValidators, addedValidators...) + systemStateMock := new(systemStateMock) + systemStateMock.On("GetValidatorSet").Return(newValidators).Once() + + blockChainMock := new(blockchainMock) + blockChainMock.On("GetStateProvider", mock.Anything). + Return(NewStateProvider(transition)).Once() + blockChainMock.On("GetSystemState", mock.Anything, mock.Anything).Return(systemStateMock).Once() + + validatorSet := NewValidatorSet(validators, hclog.NewNullLogger()) + + fsm := &fsm{ + parent: parent, + blockBuilder: blockBuilderMock, + config: &PolyBFTConfig{}, + backend: blockChainMock, + isEndOfEpoch: true, + validators: validatorSet, + uptimeCounter: createTestUptimeCounter(t, validators, 10), + exitEventRootHash: types.ZeroHash, + logger: hclog.NewNullLogger(), + } + + proposal, err := fsm.BuildProposal(0) + assert.NoError(t, err) + assert.NotNil(t, proposal) + + blockExtra, err := GetIbftExtra(stateBlock.Block.Header.ExtraData) + assert.NoError(t, err) + assert.Len(t, blockExtra.Validators.Added, 2) + assert.False(t, blockExtra.Validators.IsEmpty()) + + removedValidators := [3]uint64{3, 4, 5} + + for _, addedValidator := range addedValidators { + assert.True(t, blockExtra.Validators.Added.ContainsAddress(addedValidator.Address)) + } + + for _, removedValidator := range removedValidators { + assert.True( + t, + blockExtra.Validators.Removed.IsSet(removedValidator), + fmt.Sprintf("Expected validator at index %d to be marked as removed, but it wasn't", removedValidator), + ) + } + + blockBuilderMock.AssertExpectations(t) + systemStateMock.AssertExpectations(t) + blockChainMock.AssertExpectations(t) +} + +func TestFSM_BuildProposal_NonEpochEndingBlock_ValidatorsDeltaEmpty(t *testing.T) { + t.Parallel() + + const ( + accountCount = 6 + signaturesCount = 4 + parentBlockNumber = 9 + ) + + testValidators := newTestValidators(accountCount) + extra := createTestExtra(testValidators.getPublicIdentities(), AccountSet{}, accountCount-1, signaturesCount, signaturesCount) + parent := &types.Header{Number: parentBlockNumber, ExtraData: extra} + parent.ComputeHash() + stateBlock := createDummyStateBlock(parentBlockNumber+1, parent.Hash, extra) + + blockBuilderMock := &blockBuilderMock{} + blockBuilderMock.On("Build", mock.Anything).Return(stateBlock).Once() + blockBuilderMock.On("Fill").Once() + blockBuilderMock.On("Reset").Return(error(nil)).Once() + + systemStateMock := new(systemStateMock) + + fsm := &fsm{parent: parent, blockBuilder: blockBuilderMock, + config: &PolyBFTConfig{}, backend: &blockchainMock{}, + isEndOfEpoch: false, validators: testValidators.toValidatorSet(), + exitEventRootHash: types.ZeroHash, logger: hclog.NewNullLogger()} + + proposal, err := fsm.BuildProposal(0) + assert.NoError(t, err) + assert.NotNil(t, proposal) + + blockExtra, err := GetIbftExtra(stateBlock.Block.Header.ExtraData) + assert.NoError(t, err) + assert.True(t, blockExtra.Validators.IsEmpty()) + + blockBuilderMock.AssertExpectations(t) + systemStateMock.AssertNotCalled(t, "GetValidatorSet") +} + +func TestFSM_BuildProposal_EpochEndingBlock_FailToCreateValidatorsDelta(t *testing.T) { + t.Parallel() + + const ( + accountCount = 6 + signaturesCount = 4 + parentBlockNumber = 49 + ) + + testValidators := newTestValidators(accountCount) + allAccounts := testValidators.getPublicIdentities() + extra := createTestExtra(allAccounts, AccountSet{}, accountCount-1, signaturesCount, signaturesCount) + + parent := &types.Header{Number: parentBlockNumber, ExtraData: extra} + + transition := &state.Transition{} + blockBuilderMock := new(blockBuilderMock) + blockBuilderMock.On("WriteTx", mock.Anything).Return(error(nil)).Once() + blockBuilderMock.On("GetState").Return(transition).Once() + blockBuilderMock.On("Reset").Return(error(nil)).Once() + + systemStateMock := new(systemStateMock) + systemStateMock.On("GetValidatorSet").Return(nil, errors.New("failed to get validators set")).Once() + + blockChainMock := new(blockchainMock) + blockChainMock.On("GetStateProvider", mock.Anything). + Return(NewStateProvider(transition)).Once() + blockChainMock.On("GetSystemState", mock.Anything, mock.Anything).Return(systemStateMock).Once() + + fsm := &fsm{parent: parent, + blockBuilder: blockBuilderMock, + config: &PolyBFTConfig{}, + backend: blockChainMock, + isEndOfEpoch: true, + validators: testValidators.toValidatorSet(), + uptimeCounter: createTestUptimeCounter(t, allAccounts, 10), + exitEventRootHash: types.ZeroHash, + } + + proposal, err := fsm.BuildProposal(0) + assert.ErrorContains(t, err, "failed to retrieve validator set for current block: failed to get validators set") + assert.Nil(t, proposal) + + blockBuilderMock.AssertNotCalled(t, "Build") + blockBuilderMock.AssertNotCalled(t, "Fill") + blockBuilderMock.AssertExpectations(t) + systemStateMock.AssertExpectations(t) + blockChainMock.AssertExpectations(t) +} + +func TestFSM_VerifyStateTransactions_MiddleOfEpochWithTransaction(t *testing.T) { + t.Parallel() + + fsm := &fsm{config: &PolyBFTConfig{}, uptimeCounter: createTestUptimeCounter(t, nil, 10)} + tx, err := fsm.createValidatorsUptimeTx() + assert.NoError(t, err) + err = fsm.VerifyStateTransactions([]*types.Transaction{tx}) + assert.ErrorContains(t, err, err.Error()) +} + +func TestFSM_VerifyStateTransactions_MiddleOfEpochWithoutTransaction(t *testing.T) { + t.Parallel() + + fsm := &fsm{config: &PolyBFTConfig{}, uptimeCounter: createTestUptimeCounter(t, nil, 10)} + err := fsm.VerifyStateTransactions([]*types.Transaction{}) + assert.NoError(t, err) +} + +func TestFSM_VerifyStateTransactions_EndOfEpochWithoutTransaction(t *testing.T) { + t.Parallel() + + fsm := &fsm{config: &PolyBFTConfig{}, isEndOfEpoch: true, uptimeCounter: createTestUptimeCounter(t, nil, 10)} + assert.EqualError(t, fsm.VerifyStateTransactions([]*types.Transaction{}), + "uptime transaction is not found in the epoch ending block") +} + +func TestFSM_VerifyStateTransactions_EndOfEpochWrongValidatorsUptimeTx(t *testing.T) { + t.Parallel() + + fsm := &fsm{config: &PolyBFTConfig{}, isEndOfEpoch: true, uptimeCounter: createTestUptimeCounter(t, nil, 10)} + uptimeCounter, err := createTestUptimeCounter(t, nil, 5).EncodeAbi() + require.NoError(t, err) + + commitEpochTx := createStateTransactionWithData(contracts.ValidatorSetContract, uptimeCounter) + assert.ErrorContains(t, fsm.VerifyStateTransactions([]*types.Transaction{commitEpochTx}), "invalid uptime transaction") +} + +func TestFSM_VerifyStateTransactions_CommitmentTransactionAndSprintIsFalse(t *testing.T) { + t.Parallel() + + fsm := &fsm{config: &PolyBFTConfig{}} + + encodedCommitment, err := createTestCommitmentMessage(t, 1).EncodeAbi() + require.NoError(t, err) + + tx := createStateTransactionWithData(contracts.StateReceiverContract, encodedCommitment) + assert.ErrorContains(t, fsm.VerifyStateTransactions([]*types.Transaction{tx}), + "found commitment tx in block which should not contain it") +} + +func TestFSM_VerifyStateTransactions_EndOfEpochMoreThanOneUptimeTx(t *testing.T) { + t.Parallel() + + txs := make([]*types.Transaction, 2) + fsm := &fsm{config: &PolyBFTConfig{}, isEndOfEpoch: true, uptimeCounter: createTestUptimeCounter(t, nil, 10)} + + uptimeTxOne, err := fsm.createValidatorsUptimeTx() + require.NoError(t, err) + + txs[0] = uptimeTxOne + + uptimeTwo := createTestUptimeCounter(t, nil, 100) + input, err := uptimeTwo.EncodeAbi() + require.NoError(t, err) + + txs[1] = createStateTransactionWithData(types.ZeroAddress, input) + + assert.ErrorIs(t, fsm.VerifyStateTransactions(txs), errUptimeTxOnlyOneUptimeExpected) +} + +func TestFSM_VerifyStateTransactions_StateTransactionPass(t *testing.T) { + t.Parallel() + + validators := newTestValidators(5) + + validatorSet := NewValidatorSet(validators.getPublicIdentities(), hclog.NewNullLogger()) + + fsm := &fsm{ + config: &PolyBFTConfig{}, + isEndOfEpoch: true, + isEndOfSprint: true, + validators: validatorSet, + uptimeCounter: createTestUptimeCounter(t, nil, 10), + logger: hclog.NewNullLogger(), + } + txs := fsm.stateTransactions() + + // add validators uptime tx to the end of transactions list + tx, err := fsm.createValidatorsUptimeTx() + assert.NoError(t, err) + + txs = append([]*types.Transaction{tx}, txs...) + + err = fsm.VerifyStateTransactions(txs) + assert.NoError(t, err) +} + +func TestFSM_VerifyStateTransactions_StateTransactionQuorumNotReached(t *testing.T) { + t.Parallel() + + validators := newTestValidators(5) + commitment := createTestCommitment(t, validators.getPrivateIdentities()) + commitment.AggSignature = Signature{ + AggregatedSignature: []byte{1, 2}, + Bitmap: []byte{}, + } + + validatorSet := NewValidatorSet(validators.getPublicIdentities(), hclog.NewNullLogger()) + + fsm := &fsm{ + config: &PolyBFTConfig{}, + isEndOfEpoch: true, + isEndOfSprint: true, + validators: validatorSet, + proposerCommitmentToRegister: commitment, + uptimeCounter: createTestUptimeCounter(t, nil, 10), + logger: hclog.NewNullLogger(), + } + + txs := fsm.stateTransactions() + + // add validators uptime tx to the end of transactions list + tx, err := fsm.createValidatorsUptimeTx() + assert.NoError(t, err) + + txs = append([]*types.Transaction{tx}, txs...) + + err = fsm.VerifyStateTransactions(txs) + assert.ErrorContains(t, err, "quorum size not reached") +} + +func TestFSM_VerifyStateTransactions_StateTransactionInvalidSignature(t *testing.T) { + t.Parallel() + + validators := newTestValidators(5) + commitment := createTestCommitment(t, validators.getPrivateIdentities()) + nonValidators := newTestValidators(3) + aggregatedSigs := bls.Signatures{} + + nonValidators.iterAcct(nil, func(t *testValidator) { + aggregatedSigs = append(aggregatedSigs, t.mustSign([]byte("dummyHash"))) + }) + + sig, err := aggregatedSigs.Aggregate().Marshal() + require.NoError(t, err) + + commitment.AggSignature.AggregatedSignature = sig + + validatorSet := NewValidatorSet(validators.getPublicIdentities(), hclog.NewNullLogger()) + + fsm := &fsm{ + config: &PolyBFTConfig{}, + isEndOfEpoch: true, + isEndOfSprint: true, + validators: validatorSet, + proposerCommitmentToRegister: commitment, + uptimeCounter: createTestUptimeCounter(t, nil, 10), + logger: hclog.NewNullLogger(), + } + + txs := fsm.stateTransactions() + + // add validators uptime tx to the end of transactions list + tx, err := fsm.createValidatorsUptimeTx() + assert.NoError(t, err) + + txs = append([]*types.Transaction{tx}, txs...) + + err = fsm.VerifyStateTransactions(txs) + assert.ErrorContains(t, err, "invalid signature") +} + +func TestFSM_ValidateCommit_WrongValidator(t *testing.T) { + t.Parallel() + + const ( + accountsCount = 5 + parentBlockNumber = 10 + ) + + validators := newTestValidators(accountsCount) + parent := &types.Header{ + Number: parentBlockNumber, + ExtraData: createTestExtra(validators.getPublicIdentities(), AccountSet{}, 5, 3, 3), + } + parent.ComputeHash() + stateBlock := createDummyStateBlock(parentBlockNumber+1, parent.Hash, parent.ExtraData) + mBlockBuilder := newBlockBuilderMock(stateBlock) + + fsm := &fsm{parent: parent, blockBuilder: mBlockBuilder, config: &PolyBFTConfig{}, backend: &blockchainMock{}, + validators: validators.toValidatorSet(), logger: hclog.NewNullLogger(), exitEventRootHash: types.ZeroHash} + + _, err := fsm.BuildProposal(0) + require.NoError(t, err) + + err = fsm.ValidateCommit([]byte("0x7467674"), types.ZeroAddress.Bytes(), []byte{}) + require.ErrorContains(t, err, "unable to resolve validator") +} + +func TestFSM_ValidateCommit_InvalidHash(t *testing.T) { + t.Parallel() + + const ( + accountsCount = 5 + parentBlockNumber = 10 + ) + + validators := newTestValidators(accountsCount) + + parent := &types.Header{ + Number: parentBlockNumber, + ExtraData: createTestExtra(validators.getPublicIdentities(), AccountSet{}, 5, 3, 3), + } + parent.ComputeHash() + stateBlock := createDummyStateBlock(parentBlockNumber+1, parent.Hash, parent.ExtraData) + mBlockBuilder := newBlockBuilderMock(stateBlock) + + fsm := &fsm{parent: parent, blockBuilder: mBlockBuilder, config: &PolyBFTConfig{}, backend: &blockchainMock{}, + validators: validators.toValidatorSet(), exitEventRootHash: types.ZeroHash, logger: hclog.NewNullLogger()} + + _, err := fsm.BuildProposal(0) + assert.NoError(t, err) + + nonValidatorAcc := newTestValidator("non_validator", 1) + wrongSignature, err := nonValidatorAcc.mustSign([]byte("Foo")).Marshal() + require.NoError(t, err) + + err = fsm.ValidateCommit(validators.getValidator("0").Address().Bytes(), wrongSignature, []byte{}) + require.ErrorContains(t, err, "incorrect commit signature from") +} + +func TestFSM_ValidateCommit_Good(t *testing.T) { + t.Parallel() + + const parentBlockNumber = 10 + + validators := newTestValidatorsWithAliases([]string{"A", "B", "C", "D", "E"}) + validatorsMetadata := validators.getPublicIdentities() + + parent := &types.Header{Number: parentBlockNumber, ExtraData: createTestExtra(validatorsMetadata, AccountSet{}, 5, 3, 3)} + parent.ComputeHash() + stateBlock := createDummyStateBlock(parentBlockNumber+1, parent.Hash, parent.ExtraData) + mBlockBuilder := newBlockBuilderMock(stateBlock) + + validatorSet := NewValidatorSet(validatorsMetadata, hclog.NewNullLogger()) + + fsm := &fsm{parent: parent, blockBuilder: mBlockBuilder, config: &PolyBFTConfig{}, backend: &blockchainMock{}, + validators: validatorSet, + exitEventRootHash: types.ZeroHash, + logger: hclog.NewNullLogger()} + + proposal, err := fsm.BuildProposal(0) + require.NoError(t, err) + + block := types.Block{} + require.NoError(t, block.UnmarshalRLP(proposal)) + + validator := validators.getValidator("A") + seal, err := validator.mustSign(block.Hash().Bytes()).Marshal() + require.NoError(t, err) + err = fsm.ValidateCommit(validator.Key().Address().Bytes(), seal, block.Hash().Bytes()) + require.NoError(t, err) +} + +func TestFSM_Validate_IncorrectHeaderParentHash(t *testing.T) { + t.Parallel() + + const ( + accountsCount = 5 + parentBlockNumber = 25 + signaturesCount = 3 + ) + + validators := newTestValidators(accountsCount) + parent := &types.Header{ + Number: parentBlockNumber, + ExtraData: createTestExtra(validators.getPublicIdentities(), AccountSet{}, 4, signaturesCount, signaturesCount), + } + parent.ComputeHash() + + fsm := &fsm{parent: parent, config: &PolyBFTConfig{}, backend: &blockchainMock{}, + validators: validators.toValidatorSet(), logger: hclog.NewNullLogger()} + + stateBlock := createDummyStateBlock(parent.Number+1, types.Hash{100, 15}, parent.ExtraData) + + hash, err := new(CheckpointData).Hash(fsm.backend.GetChainID(), stateBlock.Block.Number(), stateBlock.Block.Hash()) + require.NoError(t, err) + + stateBlock.Block.Header.Hash = hash + proposal := stateBlock.Block.MarshalRLP() + + err = fsm.Validate(proposal) + require.ErrorContains(t, err, "incorrect header parent hash") +} + +func TestFSM_Validate_InvalidNumber(t *testing.T) { + t.Parallel() + + const ( + accountsCount = 5 + parentBlockNumber = 10 + signaturesCount = 3 + ) + + validators := newTestValidators(accountsCount) + parent := &types.Header{ + Number: parentBlockNumber, + ExtraData: createTestExtra(validators.getPublicIdentities(), AccountSet{}, 4, signaturesCount, signaturesCount), + } + parent.ComputeHash() + + // try some invalid block numbers, parentBlockNumber + 1 should be correct + for _, blockNum := range []uint64{parentBlockNumber - 1, parentBlockNumber, parentBlockNumber + 2} { + stateBlock := createDummyStateBlock(blockNum, parent.Hash, parent.ExtraData) + mBlockBuilder := newBlockBuilderMock(stateBlock) + fsm := &fsm{parent: parent, blockBuilder: mBlockBuilder, config: &PolyBFTConfig{}, backend: &blockchainMock{}, + validators: validators.toValidatorSet(), logger: hclog.NewNullLogger()} + + proposalHash, err := new(CheckpointData).Hash(fsm.backend.GetChainID(), stateBlock.Block.Number(), stateBlock.Block.Hash()) + require.NoError(t, err) + + stateBlock.Block.Header.Hash = proposalHash + proposal := stateBlock.Block.MarshalRLP() + + err = fsm.Validate(proposal) + require.ErrorContains(t, err, "invalid number") + } +} + +func TestFSM_Validate_TimestampOlder(t *testing.T) { + t.Parallel() + + const parentBlockNumber = 10 + + validators := newTestValidators(5) + parent := &types.Header{ + Number: parentBlockNumber, + ExtraData: createTestExtra(validators.getPublicIdentities(), AccountSet{}, 4, 3, 3), + Timestamp: uint64(time.Now().Unix()), + } + parent.ComputeHash() + + // try some invalid times + for _, blockTime := range []uint64{parent.Timestamp - 1, parent.Timestamp} { + header := &types.Header{ + Number: parentBlockNumber + 1, + ParentHash: parent.Hash, + Timestamp: blockTime, + ExtraData: parent.ExtraData, + } + stateBlock := &types.FullBlock{Block: consensus.BuildBlock(consensus.BuildBlockParams{Header: header})} + fsm := &fsm{parent: parent, config: &PolyBFTConfig{}, backend: &blockchainMock{}, + validators: validators.toValidatorSet(), logger: hclog.NewNullLogger()} + + checkpointHash, err := new(CheckpointData).Hash(fsm.backend.GetChainID(), header.Number, header.Hash) + require.NoError(t, err) + + stateBlock.Block.Header.Hash = checkpointHash + proposal := stateBlock.Block.MarshalRLP() + + err = fsm.Validate(proposal) + assert.ErrorContains(t, err, "timestamp older than parent") + } +} + +func TestFSM_Validate_IncorrectMixHash(t *testing.T) { + t.Parallel() + + const parentBlockNumber = 10 + + validators := newTestValidators(5) + parent := &types.Header{ + Number: parentBlockNumber, + ExtraData: createTestExtra(validators.getPublicIdentities(), AccountSet{}, 4, 3, 3), + Timestamp: uint64(100), + } + parent.ComputeHash() + + header := &types.Header{ + Number: parentBlockNumber + 1, + ParentHash: parent.Hash, + Timestamp: parent.Timestamp + 1, + MixHash: types.Hash{}, + ExtraData: parent.ExtraData, + } + + buildBlock := &types.FullBlock{Block: consensus.BuildBlock(consensus.BuildBlockParams{Header: header})} + + fsm := &fsm{ + parent: parent, + config: &PolyBFTConfig{}, + backend: &blockchainMock{}, + validators: validators.toValidatorSet(), + logger: hclog.NewNullLogger(), + } + rlpBlock := buildBlock.Block.MarshalRLP() + + _, err := new(CheckpointData).Hash(fsm.backend.GetChainID(), header.Number, header.Hash) + require.NoError(t, err) + + err = fsm.Validate(rlpBlock) + assert.ErrorContains(t, err, "mix digest is not correct") +} + +func TestFSM_Insert_Good(t *testing.T) { + t.Parallel() + + const ( + accountCount = 5 + parentBlockNumber = uint64(10) + signaturesCount = 3 + ) + + validators := newTestValidators(accountCount) + allAccounts := validators.getPrivateIdentities() + validatorsMetadata := validators.getPublicIdentities() + + extraParent := createTestExtra(validatorsMetadata, AccountSet{}, len(allAccounts)-1, signaturesCount, signaturesCount) + parent := &types.Header{Number: parentBlockNumber, ExtraData: extraParent} + extraBlock := createTestExtra(validatorsMetadata, AccountSet{}, len(allAccounts)-1, signaturesCount, signaturesCount) + finalBlock := consensus.BuildBlock(consensus.BuildBlockParams{ + Header: &types.Header{Number: parentBlockNumber + 1, ParentHash: parent.Hash, ExtraData: extraBlock}, + }) + + buildBlock := &types.FullBlock{Block: finalBlock} + mBlockBuilder := newBlockBuilderMock(buildBlock) + mBackendMock := &blockchainMock{} + mBackendMock.On("CommitBlock", mock.MatchedBy(func(i interface{}) bool { + stateBlock, ok := i.(*types.FullBlock) + require.True(t, ok) + + return stateBlock.Block.Number() == buildBlock.Block.Number() && stateBlock.Block.Hash() == buildBlock.Block.Hash() + })).Return(error(nil)).Once() + + validatorSet := NewValidatorSet(validatorsMetadata[0:len(validatorsMetadata)-1], hclog.NewNullLogger()) + + fsm := &fsm{parent: parent, + blockBuilder: mBlockBuilder, + config: &PolyBFTConfig{}, + backend: mBackendMock, + validators: validatorSet, + } + + var commitedSeals []*messages.CommittedSeal + + for i := 0; i < signaturesCount; i++ { + sign, err := allAccounts[i].Bls.Sign(buildBlock.Block.Hash().Bytes()) + assert.NoError(t, err) + sigRaw, err := sign.Marshal() + assert.NoError(t, err) + + commitedSeals = append(commitedSeals, &messages.CommittedSeal{ + Signer: validatorsMetadata[i].Address.Bytes(), + Signature: sigRaw, + }) + } + + proposal := buildBlock.Block.MarshalRLP() + + fsm.target = buildBlock + + fullBlock, err := fsm.Insert(proposal, commitedSeals) + + require.NoError(t, err) + mBackendMock.AssertExpectations(t) + assert.Equal(t, parentBlockNumber+1, fullBlock.Block.Number()) +} + +func TestFSM_Insert_InvalidNode(t *testing.T) { + t.Parallel() + + const ( + accountCount = 5 + parentBlockNumber = 10 + signaturesCount = 3 + ) + + validators := newTestValidatorsWithAliases([]string{"A", "B", "C", "D", "E"}) + validatorsMetadata := validators.getPublicIdentities() + + parent := &types.Header{Number: parentBlockNumber} + parent.ComputeHash() + + extraBlock := createTestExtra(validatorsMetadata, AccountSet{}, len(validators.validators)-1, signaturesCount, signaturesCount) + finalBlock := consensus.BuildBlock( + consensus.BuildBlockParams{ + Header: &types.Header{Number: parentBlockNumber + 1, ParentHash: parent.Hash, ExtraData: extraBlock}, + }) + + buildBlock := &types.FullBlock{Block: finalBlock, Receipts: []*types.Receipt{}} + mBlockBuilder := newBlockBuilderMock(buildBlock) + + validatorSet := NewValidatorSet(validatorsMetadata[0:len(validatorsMetadata)-1], hclog.NewNullLogger()) + + fsm := &fsm{parent: parent, blockBuilder: mBlockBuilder, config: &PolyBFTConfig{}, backend: &blockchainMock{}, + validators: validatorSet, + } + + proposal := buildBlock.Block.MarshalRLP() + validatorA := validators.getValidator("A") + validatorB := validators.getValidator("B") + proposalHash := buildBlock.Block.Hash().Bytes() + sigA, err := validatorA.mustSign(proposalHash).Marshal() + require.NoError(t, err) + + sigB, err := validatorB.mustSign(proposalHash).Marshal() + require.NoError(t, err) + + // create test account outside of validator set + nonValidatorAccount := newTestValidator("non_validator", 1) + nonValidatorSignature, err := nonValidatorAccount.mustSign(proposalHash).Marshal() + require.NoError(t, err) + + commitedSeals := []*messages.CommittedSeal{ + {Signer: validatorA.Address().Bytes(), Signature: sigA}, + {Signer: validatorB.Address().Bytes(), Signature: sigB}, + {Signer: nonValidatorAccount.Address().Bytes(), Signature: nonValidatorSignature}, // this one should fail + } + + fsm.target = buildBlock + + _, err = fsm.Insert(proposal, commitedSeals) + assert.ErrorContains(t, err, "invalid node id") +} + +func TestFSM_Height(t *testing.T) { + t.Parallel() + + parentNumber := uint64(3) + parent := &types.Header{Number: parentNumber} + fsm := &fsm{parent: parent} + assert.Equal(t, parentNumber+1, fsm.Height()) +} + +func TestFSM_DecodeCommitmentStateTxs(t *testing.T) { + t.Parallel() + + const ( + commitmentsCount = 8 + from = 15 + eventsSize = 40 + ) + + _, signedCommitment, _ := buildCommitmentAndStateSyncs(t, eventsSize, uint64(3), from) + + f := &fsm{ + config: &PolyBFTConfig{}, + proposerCommitmentToRegister: signedCommitment, + uptimeCounter: createTestUptimeCounter(t, nil, 10), + logger: hclog.NewNullLogger(), + } + + for i, tx := range f.stateTransactions() { + decodedData, err := decodeStateTransaction(tx.Input) + require.NoError(t, err) + + decodedCommitmentMsg, ok := decodedData.(*CommitmentMessageSigned) + require.True(t, ok) + require.Equal(t, 0, i, "failed for tx number %d", i) + require.Equal(t, signedCommitment, decodedCommitmentMsg, "failed for tx number %d", i) + } +} + +func TestFSM_DecodeCommitEpochStateTx(t *testing.T) { + t.Parallel() + + commitEpoch := createTestUptimeCounter(t, nil, 10) + input, err := commitEpoch.EncodeAbi() + require.NoError(t, err) + require.NotNil(t, input) + + tx := createStateTransactionWithData(contracts.ValidatorSetContract, input) + decodedInputData, err := decodeStateTransaction(tx.Input) + require.NoError(t, err) + + decodedCommitEpoch, ok := decodedInputData.(*contractsapi.CommitEpochFunction) + require.True(t, ok) + require.True(t, commitEpoch.ID.Cmp(decodedCommitEpoch.ID) == 0) + require.NotNil(t, decodedCommitEpoch.Epoch) + require.True(t, commitEpoch.Epoch.StartBlock.Cmp(decodedCommitEpoch.Epoch.StartBlock) == 0) + require.True(t, commitEpoch.Epoch.EndBlock.Cmp(decodedCommitEpoch.Epoch.EndBlock) == 0) + require.NotNil(t, decodedCommitEpoch.Uptime) + require.True(t, commitEpoch.Uptime.TotalBlocks.Cmp(decodedCommitEpoch.Uptime.TotalBlocks) == 0) +} + +func TestFSM_VerifyStateTransaction_ValidBothTypesOfStateTransactions(t *testing.T) { + t.Parallel() + + var ( + commitments [2]*PendingCommitment + stateSyncs [2][]*contractsapi.StateSyncedEvent + signedCommitments [2]*CommitmentMessageSigned + ) + + validators := newTestValidatorsWithAliases([]string{"A", "B", "C", "D", "E"}) + commitments[0], signedCommitments[0], stateSyncs[0] = buildCommitmentAndStateSyncs(t, 10, uint64(3), 2) + commitments[1], signedCommitments[1], stateSyncs[1] = buildCommitmentAndStateSyncs(t, 10, uint64(3), 12) + + executeForValidators := func(aliases ...string) error { + for _, sc := range signedCommitments { + // add register commitment state transaction + hash, err := sc.Hash() + require.NoError(t, err) + signature := createSignature(t, validators.getPrivateIdentities(aliases...), hash) + sc.AggSignature = *signature + } + + f := &fsm{ + isEndOfSprint: true, + config: &PolyBFTConfig{}, + validators: validators.toValidatorSet(), + } + + var txns []*types.Transaction + + for i, sc := range signedCommitments { + inputData, err := sc.EncodeAbi() + require.NoError(t, err) + + if i == 0 { + tx := createStateTransactionWithData(f.config.StateReceiverAddr, inputData) + txns = append(txns, tx) + } + } + + return f.VerifyStateTransactions(txns) + } + + assert.NoError(t, executeForValidators("A", "B", "C", "D")) + assert.ErrorContains(t, executeForValidators("A", "B", "C"), "quorum size not reached for state tx") +} + +func TestFSM_VerifyStateTransaction_InvalidTypeOfStateTransactions(t *testing.T) { + t.Parallel() + + f := &fsm{ + isEndOfSprint: true, + config: &PolyBFTConfig{}, + } + + var txns []*types.Transaction + txns = append(txns, + createStateTransactionWithData(f.config.StateReceiverAddr, []byte{9, 3, 1, 1})) + + require.ErrorContains(t, f.VerifyStateTransactions(txns), "unknown state transaction") +} + +func TestFSM_VerifyStateTransaction_QuorumNotReached(t *testing.T) { + t.Parallel() + + validators := newTestValidatorsWithAliases([]string{"A", "B", "C", "D", "E", "F"}) + _, commitmentMessageSigned, _ := buildCommitmentAndStateSyncs(t, 10, uint64(3), 2) + f := &fsm{ + isEndOfSprint: true, + config: &PolyBFTConfig{}, + validators: validators.toValidatorSet(), + } + + hash, err := commitmentMessageSigned.Hash() + require.NoError(t, err) + + var txns []*types.Transaction + + signature := createSignature(t, validators.getPrivateIdentities("A", "B"), hash) + commitmentMessageSigned.AggSignature = *signature + + inputData, err := commitmentMessageSigned.EncodeAbi() + require.NoError(t, err) + + txns = append(txns, + createStateTransactionWithData(f.config.StateReceiverAddr, inputData)) + + err = f.VerifyStateTransactions(txns) + require.ErrorContains(t, err, "quorum size not reached for state tx") +} + +func TestFSM_VerifyStateTransaction_InvalidSignature(t *testing.T) { + t.Parallel() + + validators := newTestValidatorsWithAliases([]string{"A", "B", "C", "D", "E", "F"}) + _, commitmentMessageSigned, _ := buildCommitmentAndStateSyncs(t, 10, uint64(3), 2) + f := &fsm{ + isEndOfSprint: true, + config: &PolyBFTConfig{}, + validators: validators.toValidatorSet(), + } + + hash, err := commitmentMessageSigned.Hash() + require.NoError(t, err) + + var txns []*types.Transaction + + signature := createSignature(t, validators.getPrivateIdentities("A", "B", "C", "D"), hash) + invalidValidator := newTestValidator("G", 1) + invalidSignature, err := invalidValidator.mustSign([]byte("malicious message")).Marshal() + require.NoError(t, err) + + commitmentMessageSigned.AggSignature = Signature{ + Bitmap: signature.Bitmap, + AggregatedSignature: invalidSignature, + } + + inputData, err := commitmentMessageSigned.EncodeAbi() + require.NoError(t, err) + + txns = append(txns, + createStateTransactionWithData(f.config.StateReceiverAddr, inputData)) + + err = f.VerifyStateTransactions(txns) + require.ErrorContains(t, err, "invalid signature for tx") +} + +func TestFSM_VerifyStateTransaction_TwoCommitmentMessages(t *testing.T) { + t.Parallel() + + validators := newTestValidatorsWithAliases([]string{"A", "B", "C", "D", "E", "F"}) + _, commitmentMessageSigned, _ := buildCommitmentAndStateSyncs(t, 10, uint64(3), 2) + + validatorSet := NewValidatorSet(validators.getPublicIdentities(), hclog.NewNullLogger()) + + f := &fsm{ + isEndOfSprint: true, + config: &PolyBFTConfig{}, + validators: validatorSet, + } + + hash, err := commitmentMessageSigned.Hash() + require.NoError(t, err) + + var txns []*types.Transaction + + signature := createSignature(t, validators.getPrivateIdentities("A", "B", "C", "D"), hash) + commitmentMessageSigned.AggSignature = *signature + + inputData, err := commitmentMessageSigned.EncodeAbi() + require.NoError(t, err) + + txns = append(txns, + createStateTransactionWithData(f.config.StateReceiverAddr, inputData)) + inputData, err = commitmentMessageSigned.EncodeAbi() + require.NoError(t, err) + + txns = append(txns, + createStateTransactionWithData(f.config.StateReceiverAddr, inputData)) + err = f.VerifyStateTransactions(txns) + require.ErrorContains(t, err, "only one commitment tx is allowed per block") +} + +func TestFSM_Validate_FailToVerifySignatures(t *testing.T) { + t.Parallel() + + const ( + accountsCount = 5 + parentBlockNumber = 10 + signaturesCount = 3 + ) + + validators := newTestValidators(accountsCount) + validatorsMetadata := validators.getPublicIdentities() + + extra := createTestExtraObject(validatorsMetadata, AccountSet{}, 4, signaturesCount, signaturesCount) + validatorsHash, err := validatorsMetadata.Hash() + extra.Checkpoint = &CheckpointData{CurrentValidatorsHash: validatorsHash, NextValidatorsHash: validatorsHash} + parent := &types.Header{ + Number: parentBlockNumber, + ExtraData: append(make([]byte, ExtraVanity), extra.MarshalRLPTo(nil)...), + } + parent.ComputeHash() + + polybftBackendMock := new(polybftBackendMock) + polybftBackendMock.On("GetValidators", mock.Anything, mock.Anything).Return(validatorsMetadata, nil).Once() + + validatorSet := NewValidatorSet(validatorsMetadata, hclog.NewNullLogger()) + + fsm := &fsm{ + parent: parent, + config: &PolyBFTConfig{Bridge: &BridgeConfig{}}, + backend: &blockchainMock{}, + polybftBackend: polybftBackendMock, + validators: validatorSet, + logger: hclog.NewNullLogger(), + } + + finalBlock := consensus.BuildBlock(consensus.BuildBlockParams{ + Header: &types.Header{ + Number: parentBlockNumber + 1, + ParentHash: parent.Hash, + Timestamp: parent.Timestamp + 1, + MixHash: PolyBFTMixDigest, + Difficulty: 1, + ExtraData: parent.ExtraData, + }, + }) + + checkpointHash, err := new(CheckpointData).Hash(fsm.backend.GetChainID(), finalBlock.Number(), finalBlock.Hash()) + require.NoError(t, err) + + finalBlock.Header.Hash = checkpointHash + proposal := finalBlock.MarshalRLP() + + assert.ErrorContains(t, fsm.Validate(proposal), "failed to verify signatures") + + polybftBackendMock.AssertExpectations(t) +} + +func createDummyStateBlock(blockNumber uint64, parentHash types.Hash, extraData []byte) *types.FullBlock { + finalBlock := consensus.BuildBlock(consensus.BuildBlockParams{ + Header: &types.Header{ + Number: blockNumber, + ParentHash: parentHash, + Difficulty: 1, + ExtraData: extraData, + }, + }) + + return &types.FullBlock{Block: finalBlock} +} + +func createTestExtra( + allAccounts, + previousValidatorSet AccountSet, + validatorsCount, + committedSignaturesCount, + parentSignaturesCount int, +) []byte { + extraData := createTestExtraObject(allAccounts, previousValidatorSet, validatorsCount, committedSignaturesCount, parentSignaturesCount) + marshaled := extraData.MarshalRLPTo(nil) + result := make([]byte, ExtraVanity+len(marshaled)) + copy(result[ExtraVanity:], marshaled) + + return result +} + +func createTestCommitment(t *testing.T, accounts []*wallet.Account) *CommitmentMessageSigned { + t.Helper() + + bitmap := bitmap.Bitmap{} + stateSyncEvents := make([]*contractsapi.StateSyncedEvent, len(accounts)) + + for i := 0; i < len(accounts); i++ { + stateSyncEvents[i] = &contractsapi.StateSyncedEvent{ + ID: big.NewInt(int64(i)), + Sender: types.Address(accounts[i].Ecdsa.Address()), + Receiver: types.Address(accounts[0].Ecdsa.Address()), + Data: []byte{}, + } + + bitmap.Set(uint64(i)) + } + + commitment, err := NewPendingCommitment(1, stateSyncEvents) + require.NoError(t, err) + + hash, err := commitment.Hash() + require.NoError(t, err) + + var signatures bls.Signatures + + for _, a := range accounts { + signature, err := a.Bls.Sign(hash.Bytes()) + assert.NoError(t, err) + + signatures = append(signatures, signature) + } + + aggregatedSignature, err := signatures.Aggregate().Marshal() + assert.NoError(t, err) + + signature := Signature{ + AggregatedSignature: aggregatedSignature, + Bitmap: bitmap, + } + + assert.NoError(t, err) + + return &CommitmentMessageSigned{ + Message: commitment.StateSyncCommitment, + AggSignature: signature, + } +} + +func newBlockBuilderMock(stateBlock *types.FullBlock) *blockBuilderMock { + mBlockBuilder := new(blockBuilderMock) + mBlockBuilder.On("Build", mock.Anything).Return(stateBlock).Once() + mBlockBuilder.On("Fill", mock.Anything).Once() + mBlockBuilder.On("Reset", mock.Anything).Return(error(nil)).Once() + + return mBlockBuilder +} + +func createTestUptimeCounter(t *testing.T, validatorSet AccountSet, epochSize uint64) *contractsapi.CommitEpochFunction { + t.Helper() + + if validatorSet == nil { + validatorSet = newTestValidators(5).getPublicIdentities() + } + + uptime := &contractsapi.Uptime{EpochID: big.NewInt(0), TotalBlocks: big.NewInt(int64(epochSize))} + commitEpoch := &contractsapi.CommitEpochFunction{ + ID: big.NewInt(0), + Epoch: &contractsapi.Epoch{ + StartBlock: big.NewInt(1), + EndBlock: big.NewInt(int64(epochSize + 1)), + EpochRoot: types.Hash{}, + }, + Uptime: uptime, + } + indexToStart := 0 + + for i := uint64(0); i < epochSize; i++ { + validatorIndex := indexToStart + for j := 0; j < validatorSet.Len()-1; j++ { + validatorIndex = validatorIndex % validatorSet.Len() + uptime.AddValidatorUptime(validatorSet[validatorIndex].Address, 1) + validatorIndex++ + } + + indexToStart = (indexToStart + 1) % validatorSet.Len() + } + + return commitEpoch +} + +func createTestExtraObject(allAccounts, + previousValidatorSet AccountSet, + validatorsCount, + committedSignaturesCount, + parentSignaturesCount int) *Extra { + accountCount := len(allAccounts) + dummySignature := [64]byte{} + bitmapCommitted, bitmapParent := bitmap.Bitmap{}, bitmap.Bitmap{} + extraData := &Extra{} + extraData.Validators = generateValidatorDelta(validatorsCount, allAccounts, previousValidatorSet) + + for j := range rand.Perm(accountCount)[:committedSignaturesCount] { + bitmapCommitted.Set(uint64(j)) + } + + for j := range rand.Perm(accountCount)[:parentSignaturesCount] { + bitmapParent.Set(uint64(j)) + } + + extraData.Parent = &Signature{Bitmap: bitmapCommitted, AggregatedSignature: dummySignature[:]} + extraData.Committed = &Signature{Bitmap: bitmapParent, AggregatedSignature: dummySignature[:]} + extraData.Checkpoint = &CheckpointData{} + + return extraData +} + +func generateValidatorDelta(validatorCount int, allAccounts, previousValidatorSet AccountSet) (vd *ValidatorSetDelta) { + oldMap := make(map[types.Address]int, previousValidatorSet.Len()) + for i, x := range previousValidatorSet { + oldMap[x.Address] = i + } + + vd = &ValidatorSetDelta{} + vd.Removed = bitmap.Bitmap{} + + for _, id := range rand.Perm(len(allAccounts))[:validatorCount] { + _, exists := oldMap[allAccounts[id].Address] + if !exists { + vd.Added = append(vd.Added, allAccounts[id]) + } + + delete(oldMap, allAccounts[id].Address) + } + + for _, v := range oldMap { + vd.Removed.Set(uint64(v)) + } + + return +} diff --git a/consensus/polybft/handlers.go b/consensus/polybft/handlers.go new file mode 100644 index 0000000000..f566b276b7 --- /dev/null +++ b/consensus/polybft/handlers.go @@ -0,0 +1,27 @@ +package polybft + +import "github.com/0xPolygon/polygon-edge/types" + +type PostBlockRequest struct { + // FullBlock is a reference of the executed block + FullBlock *types.FullBlock + // Epoch is the epoch number of the executed block + Epoch uint64 + // IsEpochEndingBlock indicates if this was the last block of given epoch + IsEpochEndingBlock bool +} + +type PostEpochRequest struct { + // NewEpochID is the id of the new epoch + NewEpochID uint64 + + // FirstBlockOfEpoch is the number of the epoch beginning block + FirstBlockOfEpoch uint64 + + // SystemState is the state of the governance smart contracts + // after this block + SystemState SystemState + + // ValidatorSet is the validator set for the new epoch + ValidatorSet *validatorSet +} diff --git a/consensus/polybft/hash.go b/consensus/polybft/hash.go new file mode 100644 index 0000000000..810b80cbdd --- /dev/null +++ b/consensus/polybft/hash.go @@ -0,0 +1,33 @@ +package polybft + +import ( + "sync" + + "github.com/0xPolygon/polygon-edge/types" +) + +var setupHeaderHashFuncOnce sync.Once + +// polyBFTHeaderHash defines the custom implementation for getting the header hash, +// because of the extraData field +func setupHeaderHashFunc() { + setupHeaderHashFuncOnce.Do(func() { + originalHeaderHash := types.HeaderHash + + types.HeaderHash = func(h *types.Header) types.Hash { + // when hashing the block for signing we have to remove from + // the extra field the seal and committed seal items + extra, err := GetIbftExtraClean(h.ExtraData) + if err != nil { + // TODO: log error? + return types.ZeroHash + } + + // override extra data without seals and committed seal items + hh := h.Copy() + hh.ExtraData = extra + + return originalHeaderHash(hh) + } + }) +} diff --git a/consensus/polybft/hash_test.go b/consensus/polybft/hash_test.go new file mode 100644 index 0000000000..e5164cf2ab --- /dev/null +++ b/consensus/polybft/hash_test.go @@ -0,0 +1,39 @@ +package polybft + +import ( + "testing" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/bitmap" + "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/assert" +) + +func Test_setupHeaderHashFunc(t *testing.T) { + extra := &Extra{ + Validators: &ValidatorSetDelta{Removed: bitmap.Bitmap{1}}, + Parent: createSignature(t, []*wallet.Account{wallet.GenerateAccount()}, types.ZeroHash), + Checkpoint: &CheckpointData{}, + Seal: []byte{}, + Committed: &Signature{}, + } + + header := &types.Header{ + Number: 2, + GasLimit: 10000003, + Timestamp: 18, + } + + header.ExtraData = append(make([]byte, ExtraVanity), extra.MarshalRLPTo(nil)...) + notFullExtraHash := types.HeaderHash(header) + + extra.Seal = []byte{1, 2, 3, 255} + extra.Committed = createSignature(t, []*wallet.Account{wallet.GenerateAccount()}, types.ZeroHash) + header.ExtraData = append(make([]byte, ExtraVanity), extra.MarshalRLPTo(nil)...) + fullExtraHash := types.HeaderHash(header) + + assert.Equal(t, notFullExtraHash, fullExtraHash) + + header.ExtraData = []byte{1, 2, 3, 4, 100, 200, 255} + assert.Equal(t, types.ZeroHash, types.HeaderHash(header)) // to small extra data +} diff --git a/consensus/polybft/helpers_test.go b/consensus/polybft/helpers_test.go new file mode 100644 index 0000000000..9bd93368ed --- /dev/null +++ b/consensus/polybft/helpers_test.go @@ -0,0 +1,94 @@ +package polybft + +import ( + "crypto/rand" + "fmt" + "math/big" + "testing" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/bitmap" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/require" +) + +func createTestKey(t *testing.T) *wallet.Key { + t.Helper() + + return wallet.NewKey(wallet.GenerateAccount()) +} + +func createRandomTestKeys(t *testing.T, numberOfKeys int) []*wallet.Key { + t.Helper() + + result := make([]*wallet.Key, numberOfKeys, numberOfKeys) + + for i := 0; i < numberOfKeys; i++ { + result[i] = wallet.NewKey(wallet.GenerateAccount()) + } + + return result +} + +func createSignature(t *testing.T, accounts []*wallet.Account, hash types.Hash) *Signature { + t.Helper() + + var signatures bls.Signatures + + var bmp bitmap.Bitmap + for i, x := range accounts { + bmp.Set(uint64(i)) + + src, err := x.Bls.Sign(hash[:]) + require.NoError(t, err) + + signatures = append(signatures, src) + } + + aggs, err := signatures.Aggregate().Marshal() + require.NoError(t, err) + + return &Signature{AggregatedSignature: aggs, Bitmap: bmp} +} + +func generateStateSyncEvents(t *testing.T, eventsCount int, startIdx uint64) []*contractsapi.StateSyncedEvent { + t.Helper() + + stateSyncEvents := make([]*contractsapi.StateSyncedEvent, eventsCount) + for i := 0; i < eventsCount; i++ { + stateSyncEvents[i] = &contractsapi.StateSyncedEvent{ + ID: big.NewInt(int64(startIdx + uint64(i))), + Sender: types.StringToAddress(fmt.Sprintf("0x5%d", i)), + Data: generateRandomBytes(t), + } + } + + return stateSyncEvents +} + +// generateRandomBytes generates byte array with random data of 32 bytes length +func generateRandomBytes(t *testing.T) (result []byte) { + t.Helper() + + result = make([]byte, types.HashLength) + _, err := rand.Reader.Read(result) + require.NoError(t, err, "Cannot generate random byte array content.") + + return +} + +// getEpochNumber returns epoch number for given blockNumber and epochSize. +// Epoch number is derived as a result of division of block number and epoch size. +// Since epoch number is 1-based (0 block represents special case zero epoch), +// we are incrementing result by one for non epoch-ending blocks. +func getEpochNumber(t *testing.T, blockNumber, epochSize uint64) uint64 { + t.Helper() + + if isEndOfPeriod(blockNumber, epochSize) { + return blockNumber / epochSize + } + + return blockNumber/epochSize + 1 +} diff --git a/consensus/polybft/ibft_consensus.go b/consensus/polybft/ibft_consensus.go new file mode 100644 index 0000000000..5f7ebf743c --- /dev/null +++ b/consensus/polybft/ibft_consensus.go @@ -0,0 +1,43 @@ +package polybft + +import ( + "context" + + "github.com/0xPolygon/go-ibft/core" +) + +// IBFTConsensusWrapper is a convenience wrapper for the go-ibft package +type IBFTConsensusWrapper struct { + *core.IBFT +} + +func newIBFTConsensusWrapper( + logger core.Logger, + backend core.Backend, + transport core.Transport, +) *IBFTConsensusWrapper { + return &IBFTConsensusWrapper{ + IBFT: core.NewIBFT(logger, backend, transport), + } +} + +// runSequence starts the underlying consensus mechanism for the given height. +// It may be called by a single thread at any given time +// It returns channel which will be closed after c.IBFT.RunSequence is done +// and stopSequence function which can be used to halt c.IBFT.RunSequence routine from outside +func (c *IBFTConsensusWrapper) runSequence(height uint64) (<-chan struct{}, func()) { + sequenceDone := make(chan struct{}) + ctx, cancelSequence := context.WithCancel(context.Background()) + + go func() { + c.IBFT.RunSequence(ctx, height) + cancelSequence() + close(sequenceDone) + }() + + return sequenceDone, func() { + // stopSequence terminates the running IBFT sequence gracefully and waits for it to return + cancelSequence() + <-sequenceDone // waits until c.IBFT.RunSequenc routine finishes + } +} diff --git a/consensus/polybft/merkle_tree.go b/consensus/polybft/merkle_tree.go new file mode 100644 index 0000000000..b70e279eb4 --- /dev/null +++ b/consensus/polybft/merkle_tree.go @@ -0,0 +1,153 @@ +package polybft + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "hash" + "math" + + "github.com/0xPolygon/polygon-edge/crypto" + "github.com/0xPolygon/polygon-edge/types" +) + +var errLeafNotFound = errors.New("leaf not found") + +// MerkleTree is the structure for the Merkle tree. +type MerkleTree struct { + // hasher is a pointer to the hashing struct (e.g., Keccak256) + hasher hash.Hash + // data is the data from which the Merkle tree is created + data [][]byte + // nodes are the leaf and branch nodes of the Merkle tree + nodes [][]byte +} + +// NewMerkleTree creates a new Merkle tree from the provided data and using the default hashing (Keccak256). +func NewMerkleTree(data [][]byte) (*MerkleTree, error) { + return NewMerkleTreeWithHashing(data, crypto.NewKeccakState()) +} + +// NewMerkleTreeWithHashing creates a new Merkle tree from the provided data and hash type +func NewMerkleTreeWithHashing(data [][]byte, hash hash.Hash) (*MerkleTree, error) { + if len(data) == 0 { + return nil, errors.New("tree must contain at least one leaf") + } + + branchesLen := int(math.Exp2(math.Ceil(math.Log2(float64(len(data)))))) + + nodes := make([][]byte, branchesLen+len(data)+(branchesLen-len(data))) + // create leaves + for i := range data { + hash.Reset() + hash.Write(data[i]) + h := hash.Sum(nil) + nodes[i+branchesLen] = h + } + + for i := len(data) + branchesLen; i < len(nodes); i++ { + nodes[i] = make([]byte, types.HashLength) + } + + // create branches + for i := branchesLen - 1; i > 0; i-- { + hash.Reset() + hash.Write(nodes[i*2]) + hash.Write(nodes[i*2+1]) + nodes[i] = hash.Sum(nil) + } + + tree := &MerkleTree{ + hasher: hash, + nodes: nodes, + data: data, + } + + return tree, nil +} + +// LeafIndex returns the index of given leaf if found in tree +func (t *MerkleTree) LeafIndex(leaf []byte) (uint64, error) { + for i, d := range t.data { + if bytes.Equal(d, leaf) { + return uint64(i), nil + } + } + + return 0, errLeafNotFound +} + +// Hash is the Merkle Tree root hash +func (t *MerkleTree) Hash() types.Hash { + return types.BytesToHash(t.nodes[1]) +} + +// String implements the stringer interface +func (t *MerkleTree) String() string { + return hex.EncodeToString(t.Hash().Bytes()) +} + +// GenerateProof generates the proof of membership for a piece of data in the Merkle tree. +func (t *MerkleTree) GenerateProof(index uint64, height int) []types.Hash { + proofLen := int(math.Ceil(math.Log2(float64(len(t.data))))) - height + proofHashes := make([]types.Hash, proofLen) + + hashIndex := 0 + minI := uint64(math.Pow(2, float64(height+1))) - 1 + + for i := index + uint64(len(t.nodes)/2); i > minI; i /= 2 { + proofHashes[hashIndex] = types.BytesToHash(t.nodes[i^1]) + hashIndex++ + } + + return proofHashes +} + +// GenerateProofForLeaf generates the proof of membership for a piece of data in the Merkle tree. +// If the data is not present in the tree this will return an error +func (t *MerkleTree) GenerateProofForLeaf(leaf []byte, height int) ([]types.Hash, error) { + leafIndex, err := t.LeafIndex(leaf) + if err != nil { + return nil, err + } + + return t.GenerateProof(leafIndex, height), nil +} + +// VerifyProof verifies a Merkle tree proof of membership for provided data using the default hash type (Keccak256) +func VerifyProof(index uint64, leaf []byte, proof []types.Hash, root types.Hash) error { + return VerifyProofUsing(index, leaf, proof, root, crypto.NewKeccakState()) +} + +// VerifyProofUsing verifies a Merkle tree proof of membership for provided data using the provided hash type +func VerifyProofUsing(index uint64, leaf []byte, proof []types.Hash, root types.Hash, hash hash.Hash) error { + proofHash := getProofHash(index, leaf, proof, hash) + if !bytes.Equal(root.Bytes(), proofHash) { + return fmt.Errorf("leaf with index %v, not a member of merkle tree. Merkle root hash: %v", index, root) + } + + return nil +} + +func getProofHash(index uint64, leaf []byte, proof []types.Hash, hash hash.Hash) []byte { + hash.Write(leaf) + computedHash := hash.Sum(nil) + + for i := 0; i < len(proof); i++ { + hash.Reset() + + if index%2 == 0 { + hash.Write(computedHash) + hash.Write(proof[i].Bytes()) + } else { + hash.Write(proof[i].Bytes()) + hash.Write(computedHash) + } + + computedHash = hash.Sum(nil) + index /= 2 + } + + return computedHash +} diff --git a/consensus/polybft/merkle_tree_test.go b/consensus/polybft/merkle_tree_test.go new file mode 100644 index 0000000000..02d186cf5b --- /dev/null +++ b/consensus/polybft/merkle_tree_test.go @@ -0,0 +1,56 @@ +package polybft + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMerkleTree_VerifyProofs(t *testing.T) { + t.Parallel() + + const dataLen = 515 + + verifyProof := func(numOfItems uint64) { + data := make([][]byte, numOfItems) + for i := uint64(0); i < numOfItems; i++ { + data[i] = itob(i) + } + + tree, err := NewMerkleTree(data) + require.NoError(t, err) + + merkleRootHash := tree.Hash() + + for i := uint64(0); i < numOfItems; i++ { + proof := tree.GenerateProof(i, 0) + require.NoError(t, VerifyProof(i, data[i], proof, merkleRootHash)) + } + } + + // verify proofs for trees of different sizes + for i := uint64(1); i <= dataLen; i++ { + verifyProof(i) + } +} + +func TestMerkleTree_VerifyProof_InvalidProof(t *testing.T) { + t.Parallel() + + const dataLen = 10 + data := make([][]byte, dataLen) + + for i := uint64(0); i < dataLen; i++ { + data[i] = itob(i) + } + + tree, err := NewMerkleTree(data) + require.NoError(t, err) + + merkleRootHash := tree.Hash() + + proof := tree.GenerateProof(7, 0) + proof[0][0] = proof[0][0] + 1 //change the proof + + require.Error(t, VerifyProof(7, data[7], proof, merkleRootHash)) +} diff --git a/consensus/polybft/mocks_test.go b/consensus/polybft/mocks_test.go new file mode 100644 index 0000000000..005f4711e7 --- /dev/null +++ b/consensus/polybft/mocks_test.go @@ -0,0 +1,556 @@ +package polybft + +import ( + "fmt" + "math/big" + "sort" + "strconv" + "time" + + "github.com/0xPolygon/polygon-edge/blockchain" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" + "github.com/0xPolygon/polygon-edge/helper/hex" + "github.com/0xPolygon/polygon-edge/helper/progress" + "github.com/0xPolygon/polygon-edge/state" + "github.com/0xPolygon/polygon-edge/syncer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/mock" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/contract" +) + +var _ blockchainBackend = (*blockchainMock)(nil) + +type blockchainMock struct { + mock.Mock +} + +func (m *blockchainMock) CurrentHeader() *types.Header { + args := m.Called() + + return args.Get(0).(*types.Header) //nolint:forcetypeassert +} + +func (m *blockchainMock) CommitBlock(block *types.FullBlock) error { + args := m.Called(block) + + return args.Error(0) +} + +func (m *blockchainMock) NewBlockBuilder(parent *types.Header, coinbase types.Address, + txPool txPoolInterface, blockTime time.Duration, logger hclog.Logger) (blockBuilder, error) { + args := m.Called() + + return args.Get(0).(blockBuilder), args.Error(1) //nolint:forcetypeassert +} + +func (m *blockchainMock) ProcessBlock(parent *types.Header, block *types.Block, callback func(*state.Transition) error) (*types.FullBlock, error) { + args := m.Called(parent, block, callback) + + return args.Get(0).(*types.FullBlock), args.Error(1) //nolint:forcetypeassert +} + +func (m *blockchainMock) GetStateProviderForBlock(block *types.Header) (contract.Provider, error) { + args := m.Called(block) + stateProvider, _ := args.Get(0).(contract.Provider) + + return stateProvider, nil +} + +func (m *blockchainMock) GetStateProvider(transition *state.Transition) contract.Provider { + args := m.Called() + stateProvider, _ := args.Get(0).(contract.Provider) + + return stateProvider +} + +func (m *blockchainMock) GetHeaderByNumber(number uint64) (*types.Header, bool) { + args := m.Called(number) + + if len(args) == 1 { + header, ok := args.Get(0).(*types.Header) + + if ok { + return header, true + } + + getHeaderCallback, ok := args.Get(0).(func(number uint64) *types.Header) + if ok { + h := getHeaderCallback(number) + + return h, h != nil + } + } else if len(args) == 2 { + return args.Get(0).(*types.Header), args.Get(1).(bool) //nolint:forcetypeassert + } + + panic("Unsupported mock for GetHeaderByNumber") +} + +func (m *blockchainMock) GetHeaderByHash(hash types.Hash) (*types.Header, bool) { + args := m.Called(hash) + header, ok := args.Get(0).(*types.Header) + + if ok { + return header, true + } + + getHeaderCallback, ok := args.Get(0).(func(hash types.Hash) *types.Header) + if ok { + h := getHeaderCallback(hash) + + return h, h != nil + } + + panic("Unsupported mock for GetHeaderByHash") +} + +func (m *blockchainMock) GetSystemState(config *PolyBFTConfig, provider contract.Provider) SystemState { + args := m.Called(config, provider) + + return args.Get(0).(SystemState) //nolint:forcetypeassert +} + +func (m *blockchainMock) SubscribeEvents() blockchain.Subscription { + return nil +} + +func (m *blockchainMock) CalculateGasLimit(number uint64) (uint64, error) { + return 0, nil +} + +func (m *blockchainMock) GetChainID() uint64 { + return 0 +} + +var _ polybftBackend = (*polybftBackendMock)(nil) + +type polybftBackendMock struct { + mock.Mock +} + +// GetValidators retrieves validator set for the given block +func (p *polybftBackendMock) GetValidators(blockNumber uint64, parents []*types.Header) (AccountSet, error) { + args := p.Called(blockNumber, parents) + if len(args) == 1 { + accountSet, _ := args.Get(0).(AccountSet) + + return accountSet, nil + } else if len(args) == 2 { + accountSet, _ := args.Get(0).(AccountSet) + + return accountSet, args.Error(1) + } + + panic("polybftBackendMock.GetValidators doesn't support such combination of arguments") +} + +var _ blockBuilder = (*blockBuilderMock)(nil) + +type blockBuilderMock struct { + mock.Mock +} + +func (m *blockBuilderMock) Reset() error { + args := m.Called() + if len(args) == 0 { + return nil + } + + return args.Error(0) +} + +func (m *blockBuilderMock) WriteTx(tx *types.Transaction) error { + args := m.Called(tx) + if len(args) == 0 { + return nil + } + + return args.Error(0) +} + +func (m *blockBuilderMock) Fill() { + m.Called() +} + +// Receipts returns the collection of transaction receipts for given block +func (m *blockBuilderMock) Receipts() []*types.Receipt { + args := m.Called() + + return args.Get(0).([]*types.Receipt) //nolint:forcetypeassert +} + +func (m *blockBuilderMock) Build(handler func(*types.Header)) (*types.FullBlock, error) { + args := m.Called(handler) + builtBlock := args.Get(0).(*types.FullBlock) //nolint:forcetypeassert + + handler(builtBlock.Block.Header) + + return builtBlock, nil +} + +func (m *blockBuilderMock) GetState() *state.Transition { + args := m.Called() + + return args.Get(0).(*state.Transition) //nolint:forcetypeassert +} + +var _ SystemState = (*systemStateMock)(nil) + +type systemStateMock struct { + mock.Mock +} + +func (m *systemStateMock) GetValidatorSet() (AccountSet, error) { + args := m.Called() + if len(args) == 1 { + accountSet, _ := args.Get(0).(AccountSet) + + return accountSet, nil + } else if len(args) == 2 { + accountSet, _ := args.Get(0).(AccountSet) + + return accountSet, args.Error(1) + } + + panic("systemStateMock.GetValidatorSet doesn't support such combination of arguments") +} + +func (m *systemStateMock) GetNextCommittedIndex() (uint64, error) { + args := m.Called() + + if len(args) == 1 { + index, _ := args.Get(0).(uint64) + + return index, nil + } else if len(args) == 2 { + index, _ := args.Get(0).(uint64) + + return index, args.Error(1) + } + + return 0, nil +} + +func (m *systemStateMock) GetEpoch() (uint64, error) { + args := m.Called() + if len(args) == 1 { + epochNumber, _ := args.Get(0).(uint64) + + return epochNumber, nil + } else if len(args) == 2 { + epochNumber, _ := args.Get(0).(uint64) + err, ok := args.Get(1).(error) + if ok { + return epochNumber, err + } + + return epochNumber, nil + } + + return 0, nil +} + +var _ contract.Provider = (*stateProviderMock)(nil) + +type stateProviderMock struct { + mock.Mock +} + +func (s *stateProviderMock) Call(ethgo.Address, []byte, *contract.CallOpts) ([]byte, error) { + return nil, nil +} + +func (s *stateProviderMock) Txn(ethgo.Address, ethgo.Key, []byte) (contract.Txn, error) { + return nil, nil +} + +var _ BridgeTransport = (*transportMock)(nil) + +type transportMock struct { + mock.Mock +} + +func (t *transportMock) Multicast(msg interface{}) { + _ = t.Called(msg) +} + +type testValidators struct { + validators map[string]*testValidator +} + +func newTestValidators(validatorsCount int) *testValidators { + aliases := make([]string, validatorsCount) + for i := 0; i < validatorsCount; i++ { + aliases[i] = strconv.Itoa(i) + } + + return newTestValidatorsWithAliases(aliases) +} + +func newTestValidatorsWithAliases(aliases []string, votingPowers ...[]uint64) *testValidators { + validators := map[string]*testValidator{} + + for i, alias := range aliases { + votingPower := uint64(1) + if len(votingPowers) == 1 { + votingPower = votingPowers[0][i] + } + + validators[alias] = newTestValidator(alias, votingPower) + } + + return &testValidators{validators: validators} +} + +func (v *testValidators) create(alias string, votingPower uint64) { + if _, ok := v.validators[alias]; !ok { + v.validators[alias] = newTestValidator(alias, votingPower) + } +} + +func (v *testValidators) iterAcct(aliases []string, handle func(t *testValidator)) { + if len(aliases) == 0 { + // loop over the whole set + for k := range v.validators { + aliases = append(aliases, k) + } + // sort the names since they get queried randomly + sort.Strings(aliases) + } + + for _, alias := range aliases { + handle(v.getValidator(alias)) + } +} + +func (v *testValidators) getParamValidators(aliases ...string) (res []*Validator) { + v.iterAcct(aliases, func(t *testValidator) { + res = append(res, t.paramsValidator()) + }) + + return +} + +func (v *testValidators) getValidators(aliases ...string) (res []*testValidator) { + v.iterAcct(aliases, func(t *testValidator) { + res = append(res, t) + }) + + return +} + +func (v *testValidators) getPublicIdentities(aliases ...string) (res AccountSet) { + v.iterAcct(aliases, func(t *testValidator) { + res = append(res, t.ValidatorMetadata()) + }) + + return +} + +func (v *testValidators) getPrivateIdentities(aliases ...string) (res []*wallet.Account) { + v.iterAcct(aliases, func(t *testValidator) { + res = append(res, t.account) + }) + + return +} + +func (v *testValidators) getValidator(alias string) *testValidator { + vv, ok := v.validators[alias] + if !ok { + panic(fmt.Sprintf("BUG: validator %s does not exist", alias)) + } + + return vv +} + +func (v *testValidators) toValidatorSet() *validatorSet { + return NewValidatorSet(v.getPublicIdentities(), hclog.NewNullLogger()) +} + +func (v *testValidators) updateVotingPowers(votingPowersMap map[string]uint64) AccountSet { + if len(votingPowersMap) == 0 { + return AccountSet{} + } + + aliases := []string{} + for alias := range votingPowersMap { + aliases = append(aliases, alias) + } + + v.iterAcct(aliases, func(t *testValidator) { + t.votingPower = votingPowersMap[t.alias] + }) + + return v.getPublicIdentities(aliases...) +} + +type testValidator struct { + alias string + account *wallet.Account + votingPower uint64 +} + +func newTestValidator(alias string, votingPower uint64) *testValidator { + return &testValidator{ + alias: alias, + votingPower: votingPower, + account: wallet.GenerateAccount(), + } +} + +func (v *testValidator) Address() types.Address { + return types.Address(v.account.Ecdsa.Address()) +} + +func (v *testValidator) Key() *wallet.Key { + return wallet.NewKey(v.account) +} + +func (v *testValidator) paramsValidator() *Validator { + bls := v.account.Bls.PublicKey().Marshal() + + return &Validator{ + Address: v.Address(), + BlsKey: hex.EncodeToString(bls), + Balance: big.NewInt(1000), + } +} + +func (v *testValidator) ValidatorMetadata() *ValidatorMetadata { + return &ValidatorMetadata{ + Address: types.Address(v.account.Ecdsa.Address()), + BlsKey: v.account.Bls.PublicKey(), + VotingPower: new(big.Int).SetUint64(v.votingPower), + } +} + +func (v *testValidator) mustSign(hash []byte) *bls.Signature { + signature, err := v.account.Bls.Sign(hash) + if err != nil { + panic(fmt.Sprintf("BUG: failed to sign: %v", err)) + } + + return signature +} + +type testHeadersMap struct { + headersByNumber map[uint64]*types.Header +} + +func (t *testHeadersMap) addHeader(header *types.Header) { + if t.headersByNumber == nil { + t.headersByNumber = map[uint64]*types.Header{} + } + + t.headersByNumber[header.Number] = header +} + +func (t *testHeadersMap) getHeader(number uint64) *types.Header { + return t.headersByNumber[number] +} + +func (t *testHeadersMap) getHeaderByHash(hash types.Hash) *types.Header { + for _, header := range t.headersByNumber { + if header.Hash == hash { + return header + } + } + + return nil +} + +func (t *testHeadersMap) getHeaders() []*types.Header { + headers := make([]*types.Header, 0, len(t.headersByNumber)) + for _, header := range t.headersByNumber { + headers = append(headers, header) + } + + return headers +} + +var _ txPoolInterface = (*txPoolMock)(nil) + +type txPoolMock struct { + mock.Mock +} + +func (tp *txPoolMock) Prepare() { + tp.Called() +} + +func (tp *txPoolMock) Length() uint64 { + args := tp.Called() + + return args[0].(uint64) //nolint +} + +func (tp *txPoolMock) Peek() *types.Transaction { + args := tp.Called() + + return args[0].(*types.Transaction) //nolint +} + +func (tp *txPoolMock) Pop(tx *types.Transaction) { + tp.Called(tx) +} + +func (tp *txPoolMock) Drop(tx *types.Transaction) { + tp.Called(tx) +} + +func (tp *txPoolMock) Demote(tx *types.Transaction) { + tp.Called(tx) +} + +func (tp *txPoolMock) SetSealing(v bool) { + tp.Called(v) +} + +func (tp *txPoolMock) ResetWithHeaders(values ...*types.Header) { + tp.Called(values) +} + +var _ syncer.Syncer = (*syncerMock)(nil) + +type syncerMock struct { + mock.Mock +} + +func (tp *syncerMock) Start() error { + args := tp.Called() + + return args.Error(0) +} + +func (tp *syncerMock) Close() error { + args := tp.Called() + + return args.Error(0) +} + +func (tp *syncerMock) GetSyncProgression() *progress.Progression { + args := tp.Called() + + return args[0].(*progress.Progression) //nolint +} + +func (tp *syncerMock) HasSyncPeer() bool { + args := tp.Called() + + return args[0].(bool) //nolint +} + +func (tp *syncerMock) Sync(func(*types.FullBlock) bool) error { + args := tp.Called() + + return args.Error(0) +} + +func init() { + // setup custom hash header func + setupHeaderHashFunc() +} diff --git a/consensus/polybft/polybft.go b/consensus/polybft/polybft.go new file mode 100644 index 0000000000..8741038a6c --- /dev/null +++ b/consensus/polybft/polybft.go @@ -0,0 +1,509 @@ +// Package polybft implements PBFT consensus algorithm integration and bridge feature +package polybft + +import ( + "encoding/json" + "fmt" + "path/filepath" + "time" + + "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/consensus" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/helper/common" + "github.com/0xPolygon/polygon-edge/helper/progress" + "github.com/0xPolygon/polygon-edge/network" + "github.com/0xPolygon/polygon-edge/state" + "github.com/0xPolygon/polygon-edge/syncer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" +) + +const ( + minSyncPeers = 2 + pbftProto = "/pbft/0.2" + bridgeProto = "/bridge/0.2" + DisclaimerMessage = "**** POLYBFT CONSENSUS PROTOCOL IS IN EXPERIMENTAL PHASE AND IS NOT FULLY PRODUCTION READY. " + + "YOU ARE USING IT AT YOUR OWN RISK. ****" +) + +// polybftBackend is an interface defining polybft methods needed by fsm and sync tracker +type polybftBackend interface { + // GetValidators retrieves validator set for the given block + GetValidators(blockNumber uint64, parents []*types.Header) (AccountSet, error) +} + +// Factory is the factory function to create a discovery consensus +func Factory(params *consensus.Params) (consensus.Consensus, error) { + logger := params.Logger.Named("polybft") + + setupHeaderHashFunc() + + polybft := &Polybft{ + config: params, + closeCh: make(chan struct{}), + logger: logger, + txPool: params.TxPool, + } + + // initialize polybft consensus config + customConfigJSON, err := json.Marshal(params.Config.Config) + if err != nil { + return nil, err + } + + err = json.Unmarshal(customConfigJSON, &polybft.consensusConfig) + if err != nil { + return nil, err + } + + return polybft, nil +} + +type Polybft struct { + // closeCh is used to signal that consensus protocol is stopped + closeCh chan struct{} + + // ibft is the ibft engine + ibft *IBFTConsensusWrapper + + // state is reference to the struct which encapsulates consensus data persistence logic + state *State + + // consensus parametres + config *consensus.Params + + // consensusConfig is genesis configuration for polybft consensus protocol + consensusConfig *PolyBFTConfig + + // blockchain is a reference to the blockchain object + blockchain blockchainBackend + + // runtime handles consensus runtime features like epoch, state and event management + runtime *consensusRuntime + + // block time duration + blockTime time.Duration + + // dataDir is the data directory to store the info + dataDir string + + // reference to the syncer + syncer syncer.Syncer + + // topic for consensus engine messages + consensusTopic *network.Topic + + // topic for bridge messages + bridgeTopic *network.Topic + + // key encapsulates ECDSA address and BLS signing logic + key *wallet.Key + + // validatorsCache represents cache of validators snapshots + validatorsCache *validatorsSnapshotCache + + // logger + logger hclog.Logger + + // tx pool as interface + txPool txPoolInterface +} + +func GenesisPostHookFactory(config *chain.Chain, engineName string) func(txn *state.Transition) error { + return func(transition *state.Transition) error { + polyBFTConfig, err := GetPolyBFTConfig(config) + if err != nil { + return err + } + + // Initialize child validator set + input, err := getInitChildValidatorSetInput(polyBFTConfig) + if err != nil { + return err + } + + if err = initContract(contracts.ValidatorSetContract, input, "ChildValidatorSet", transition); err != nil { + return err + } + + if err != nil { + return fmt.Errorf("failed loading rootchain manifest: %w", err) + } + + rootchainAdmin := types.ZeroAddress + if polyBFTConfig.IsBridgeEnabled() { + rootchainAdmin = polyBFTConfig.Bridge.AdminAddress + } + + input, err = contractsapi.MRC20.Abi.Methods["initialize"].Encode( + []interface{}{rootchainAdmin, nativeTokenName, nativeTokenSymbol}) + if err != nil { + return err + } + + return initContract(contracts.NativeTokenContract, input, "MRC20", transition) + } +} + +// Initialize initializes the consensus (e.g. setup data) +func (p *Polybft) Initialize() error { + p.logger.Info("initializing polybft...") + + // read account + account, err := wallet.NewAccountFromSecret(p.config.SecretsManager) + if err != nil { + return fmt.Errorf("failed to read account data. Error: %w", err) + } + + // set key + p.key = wallet.NewKey(account) + + // create and set syncer + p.syncer = syncer.NewSyncer( + p.config.Logger.Named("syncer"), + p.config.Network, + p.config.Blockchain, + time.Duration(p.config.BlockTime)*3*time.Second, + ) + + // set blockchain backend + p.blockchain = &blockchainWrapper{ + blockchain: p.config.Blockchain, + executor: p.config.Executor, + } + + // create bridge and consensus topics + if err = p.createTopics(); err != nil { + return fmt.Errorf("cannot create topics: %w", err) + } + + // set block time + p.blockTime = time.Duration(p.config.BlockTime) + + // initialize polybft consensus data directory + p.dataDir = filepath.Join(p.config.Config.Path, "polybft") + // create the data dir if not exists + if err = common.CreateDirSafe(p.dataDir, 0750); err != nil { + return fmt.Errorf("failed to create data directory. Error: %w", err) + } + + stt, err := newState(filepath.Join(p.dataDir, stateFileName), p.logger, p.closeCh) + if err != nil { + return fmt.Errorf("failed to create state instance. Error: %w", err) + } + + p.state = stt + p.validatorsCache = newValidatorsSnapshotCache(p.config.Logger, stt, p.blockchain) + + // create runtime + if err := p.initRuntime(); err != nil { + return err + } + + p.ibft = newIBFTConsensusWrapper(p.logger, p.runtime, p) + + if err = p.subscribeToIbftTopic(); err != nil { + return fmt.Errorf("IBFT topic subscription failed: %w", err) + } + + return nil +} + +// Start starts the consensus and servers +func (p *Polybft) Start() error { + p.logger.Warn(DisclaimerMessage) + p.logger.Info("starting polybft consensus", "signer", p.key.String()) + + // start syncer (also initializes peer map) + if err := p.syncer.Start(); err != nil { + return fmt.Errorf("failed to start syncer. Error: %w", err) + } + + // start syncing + go func() { + blockHandler := func(b *types.FullBlock) bool { + p.runtime.OnBlockInserted(b) + + return false + } + + if err := p.syncer.Sync(blockHandler); err != nil { + p.logger.Error("blocks synchronization failed", "error", err) + } + }() + + // start consensus runtime + if err := p.startRuntime(); err != nil { + return fmt.Errorf("consensus runtime start failed: %w", err) + } + + // start state DB process + go p.state.startStatsReleasing() + + return nil +} + +// initRuntime creates consensus runtime +func (p *Polybft) initRuntime() error { + runtimeConfig := &runtimeConfig{ + PolyBFTConfig: p.consensusConfig, + Key: p.key, + DataDir: p.dataDir, + State: p.state, + blockchain: p.blockchain, + polybftBackend: p, + txPool: p.txPool, + bridgeTopic: p.bridgeTopic, + } + + runtime, err := newConsensusRuntime(p.logger, runtimeConfig) + if err != nil { + return err + } + + p.runtime = runtime + + return nil +} + +// startRuntime starts consensus runtime +func (p *Polybft) startRuntime() error { + go p.startConsensusProtocol() + + return nil +} + +func (p *Polybft) startConsensusProtocol() { + // wait to have at least n peers connected. The 2 is just an initial heuristic value + // Most likely we will parametrize this in the future. + if !p.waitForNPeers() { + return + } + + newBlockSub := p.blockchain.SubscribeEvents() + defer newBlockSub.Close() + + syncerBlockCh := make(chan struct{}) + + go func() { + eventCh := newBlockSub.GetEventCh() + + for { + select { + case <-p.closeCh: + return + case ev := <-eventCh: + // The blockchain notification system can eventually deliver + // stale block notifications. These should be ignored + if ev.Source == "syncer" && ev.NewChain[0].Number > p.blockchain.CurrentHeader().Number { + syncerBlockCh <- struct{}{} + } + } + } + }() + + var ( + sequenceCh <-chan struct{} + stopSequence func() + ) + + for { + latestHeader := p.blockchain.CurrentHeader() + + currentValidators, err := p.GetValidators(latestHeader.Number, nil) + if err != nil { + p.logger.Error("failed to query current validator set", "block number", latestHeader.Number, "error", err) + } + + isValidator := currentValidators.ContainsNodeID(p.key.String()) + p.runtime.setIsActiveValidator(isValidator) + + p.txPool.SetSealing(isValidator) // update tx pool + + if isValidator { + // initialze FSM as a stateless ibft backend via runtime as an adapter + err = p.runtime.FSM() + if err != nil { + p.logger.Error("failed to create fsm", "block number", latestHeader.Number, "error", err) + + continue + } + + sequenceCh, stopSequence = p.ibft.runSequence(latestHeader.Number + 1) + } + + now := time.Now() + + select { + case <-syncerBlockCh: + if isValidator { + stopSequence() + p.logger.Info("canceled sequence", "sequence", latestHeader.Number+1) + } + case <-sequenceCh: + case <-p.closeCh: + if isValidator { + stopSequence() + } + + return + } + + p.logger.Debug("time to run the sequence", "seconds", time.Since(now)) + } +} + +func (p *Polybft) waitForNPeers() bool { + for { + select { + case <-p.closeCh: + return false + case <-time.After(2 * time.Second): + } + + if len(p.config.Network.Peers()) >= minSyncPeers { + break + } + } + + return true +} + +// Close closes the connection +func (p *Polybft) Close() error { + if p.syncer != nil { + if err := p.syncer.Close(); err != nil { + return err + } + } + + close(p.closeCh) + p.runtime.close() + + return nil +} + +// GetSyncProgression retrieves the current sync progression, if any +func (p *Polybft) GetSyncProgression() *progress.Progression { + return p.syncer.GetSyncProgression() +} + +// VerifyHeader implements consensus.Engine and checks whether a header conforms to the consensus rules +func (p *Polybft) VerifyHeader(header *types.Header) error { + // Short circuit if the header is known + if _, ok := p.blockchain.GetHeaderByHash(header.Hash); ok { + return nil + } + + parent, ok := p.blockchain.GetHeaderByHash(header.ParentHash) + if !ok { + return fmt.Errorf( + "unable to get parent header by hash for block number %d", + header.Number, + ) + } + + return p.verifyHeaderImpl(parent, header, nil) +} + +func (p *Polybft) verifyHeaderImpl(parent, header *types.Header, parents []*types.Header) error { + blockNumber := header.Number + + // validate header fields + if err := validateHeaderFields(parent, header); err != nil { + return fmt.Errorf("failed to validate header for block %d. error = %w", blockNumber, err) + } + + validators, err := p.GetValidators(blockNumber-1, parents) + if err != nil { + return fmt.Errorf("failed to validate header for block %d. could not retrieve block validators:%w", blockNumber, err) + } + + // decode the extra field and validate the signatures + extra, err := GetIbftExtra(header.ExtraData) + if err != nil { + return fmt.Errorf("failed to verify header for block %d. get extra error = %w", blockNumber, err) + } + + parentExtra, err := GetIbftExtra(parent.ExtraData) + if err != nil { + return err + } + + if err := extra.ValidateBasic(parentExtra); err != nil { + return err + } + + if extra.Committed == nil { + return fmt.Errorf("failed to verify signatures for block %d because signatures are not present", blockNumber) + } + + checkpointHash, err := extra.Checkpoint.Hash(p.blockchain.GetChainID(), header.Number, header.Hash) + if err != nil { + return fmt.Errorf("failed to calculate proposal hash: %w", err) + } + + // TODO: Move signature validation logic to Extra + if err := extra.Committed.VerifyCommittedFields(validators, checkpointHash, p.logger); err != nil { + return fmt.Errorf("failed to verify signatures for block %d. Signed hash %v: %w", + blockNumber, checkpointHash, err) + } + + // validate the signatures for parent (skip block 1 because genesis does not have committed) + if blockNumber > 1 { + if extra.Parent == nil { + return fmt.Errorf("failed to verify signatures for parent of block %d because signatures are not present", + blockNumber) + } + + parentValidators, err := p.GetValidators(blockNumber-2, parents) + if err != nil { + return fmt.Errorf( + "failed to validate header for block %d. could not retrieve parent validators: %w", + blockNumber, + err, + ) + } + + parentCheckpointHash, err := parentExtra.Checkpoint.Hash(p.blockchain.GetChainID(), parent.Number, parent.Hash) + if err != nil { + return fmt.Errorf("failed to calculate parent proposal hash: %w", err) + } + + if err := extra.Parent.VerifyCommittedFields(parentValidators, parentCheckpointHash, p.logger); err != nil { + return fmt.Errorf("failed to verify signatures for parent of block %d. Signed hash: %v: %w", + blockNumber, parentCheckpointHash, err) + } + } + + return nil +} + +func (p *Polybft) GetValidators(blockNumber uint64, parents []*types.Header) (AccountSet, error) { + return p.validatorsCache.GetSnapshot(blockNumber, parents) +} + +// ProcessHeaders updates the snapshot based on the verified headers +func (p *Polybft) ProcessHeaders(_ []*types.Header) error { + // Not required + return nil +} + +// GetBlockCreator retrieves the block creator (or signer) given the block header +func (p *Polybft) GetBlockCreator(h *types.Header) (types.Address, error) { + return types.BytesToAddress(h.Miner), nil +} + +// PreCommitState a hook to be called before finalizing state transition on inserting block +func (p *Polybft) PreCommitState(_ *types.Header, _ *state.Transition) error { + // Not required + return nil +} + +// GetBridgeProvider returns an instance of BridgeDataProvider +func (p *Polybft) GetBridgeProvider() consensus.BridgeDataProvider { + return p.runtime +} diff --git a/consensus/polybft/polybft_config.go b/consensus/polybft/polybft_config.go new file mode 100644 index 0000000000..00851eee40 --- /dev/null +++ b/consensus/polybft/polybft_config.go @@ -0,0 +1,199 @@ +package polybft + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "math/big" + "os" + "path/filepath" + "time" + + "github.com/0xPolygon/polygon-edge/chain" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/helper/common" + "github.com/0xPolygon/polygon-edge/types" +) + +// PolyBFTConfig is the configuration file for the Polybft consensus protocol. +type PolyBFTConfig struct { + // InitialValidatorSet are the genesis validators + InitialValidatorSet []*Validator `json:"initialValidatorSet"` + + // Bridge is the rootchain bridge configuration + Bridge *BridgeConfig `json:"bridge"` + + // EpochSize is size of epoch + EpochSize uint64 `json:"epochSize"` + + // EpochReward is assigned to validators for blocks sealing + EpochReward uint64 `json:"epochReward"` + + // SprintSize is size of sprint + SprintSize uint64 `json:"sprintSize"` + + // BlockTime is target frequency of blocks production + BlockTime time.Duration `json:"blockTime"` + + // Governance is the initial governance address + Governance types.Address `json:"governance"` + + // TODO: Remove these two addresses as they are hardcoded and known in advance + // Address of the system contracts, as of now (testing) this is populated automatically during genesis + ValidatorSetAddr types.Address `json:"validatorSetAddr"` + StateReceiverAddr types.Address `json:"stateReceiverAddr"` +} + +// GetPolyBFTConfig deserializes provided chain config and returns PolyBFTConfig +func GetPolyBFTConfig(chainConfig *chain.Chain) (PolyBFTConfig, error) { + consensusConfigJSON, err := json.Marshal(chainConfig.Params.Engine["polybft"]) + if err != nil { + return PolyBFTConfig{}, err + } + + var polyBFTConfig PolyBFTConfig + err = json.Unmarshal(consensusConfigJSON, &polyBFTConfig) + + if err != nil { + return PolyBFTConfig{}, err + } + + return polyBFTConfig, nil +} + +// BridgeConfig is the rootchain bridge configuration +type BridgeConfig struct { + BridgeAddr types.Address `json:"stateSenderAddr"` + CheckpointAddr types.Address `json:"checkpointAddr"` + AdminAddress types.Address `json:"adminAddress"` + JSONRPCEndpoint string `json:"jsonRPCEndpoint"` +} + +func (p *PolyBFTConfig) IsBridgeEnabled() bool { + return p.Bridge != nil +} + +// Validator represents public information about validator accounts which are the part of genesis +type Validator struct { + Address types.Address + BlsKey string + Balance *big.Int + NodeID string +} + +type validatorRaw struct { + Address types.Address `json:"address"` + BlsKey string `json:"blsKey"` + Balance *string `json:"balance"` + NodeID string `json:"nodeId"` +} + +func (v *Validator) MarshalJSON() ([]byte, error) { + raw := &validatorRaw{Address: v.Address, BlsKey: v.BlsKey, NodeID: v.NodeID} + raw.Balance = types.EncodeBigInt(v.Balance) + + return json.Marshal(raw) +} + +func (v *Validator) UnmarshalJSON(data []byte) error { + var raw validatorRaw + + var err error + + if err = json.Unmarshal(data, &raw); err != nil { + return err + } + + v.Address = raw.Address + v.BlsKey = raw.BlsKey + v.NodeID = raw.NodeID + v.Balance, err = types.ParseUint256orHex(raw.Balance) + + if err != nil { + return err + } + + return nil +} + +// UnmarshalBLSPublicKey unmarshals the hex encoded BLS public key +func (v *Validator) UnmarshalBLSPublicKey() (*bls.PublicKey, error) { + decoded, err := hex.DecodeString(v.BlsKey) + if err != nil { + return nil, err + } + + return bls.UnmarshalPublicKey(decoded) +} + +// ToValidatorMetadata creates ValidatorMetadata instance +func (v *Validator) ToValidatorMetadata() (*ValidatorMetadata, error) { + blsKey, err := v.UnmarshalBLSPublicKey() + if err != nil { + return nil, err + } + + metadata := &ValidatorMetadata{ + Address: v.Address, + BlsKey: blsKey, + VotingPower: new(big.Int).Set(v.Balance), + } + + return metadata, nil +} + +// RootchainConfig contains information about rootchain contract addresses +// as well as rootchain admin account address +type RootchainConfig struct { + StateSenderAddress types.Address `json:"stateSenderAddress"` + CheckpointManagerAddress types.Address `json:"checkpointManagerAddress"` + BLSAddress types.Address `json:"blsAddress"` + BN256G2Address types.Address `json:"bn256G2Address"` + ExitHelperAddress types.Address `json:"exitHelperAddress"` + AdminAddress types.Address `json:"adminAddress"` +} + +// ToBridgeConfig creates BridgeConfig instance +func (r *RootchainConfig) ToBridgeConfig() *BridgeConfig { + return &BridgeConfig{ + BridgeAddr: r.StateSenderAddress, + CheckpointAddr: r.CheckpointManagerAddress, + AdminAddress: r.AdminAddress, + } +} + +// Manifest holds metadata, such as genesis validators and rootchain configuration +type Manifest struct { + GenesisValidators []*Validator `json:"validators"` + RootchainConfig *RootchainConfig `json:"rootchain"` +} + +// LoadManifest deserializes Manifest instance +func LoadManifest(metadataFile string) (*Manifest, error) { + data, err := os.ReadFile(metadataFile) + if err != nil { + return nil, err + } + + var manifest Manifest + + if err := json.Unmarshal(data, &manifest); err != nil { + return nil, err + } + + return &manifest, nil +} + +// Save marshals RootchainManifest instance to json and persists it to given location +func (m *Manifest) Save(manifestPath string) error { + data, err := json.MarshalIndent(m, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal rootchain manifest to JSON: %w", err) + } + + if err := common.SaveFileSafe(filepath.Clean(manifestPath), data, 0660); err != nil { + return fmt.Errorf("failed to save rootchain manifest file: %w", err) + } + + return nil +} diff --git a/consensus/polybft/polybft_test.go b/consensus/polybft/polybft_test.go new file mode 100644 index 0000000000..b728579dab --- /dev/null +++ b/consensus/polybft/polybft_test.go @@ -0,0 +1,266 @@ +package polybft + +import ( + "errors" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/consensus" + "github.com/0xPolygon/polygon-edge/consensus/ibft/signer" + "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" + "github.com/0xPolygon/polygon-edge/helper/progress" + "github.com/0xPolygon/polygon-edge/txpool" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +// the test initializes polybft and chain mock (map of headers) after which a new header is verified +// firstly, two invalid situation of header verifications are triggered (missing Committed field and invalid validators for ParentCommitted) +// afterwards, valid inclusion into the block chain is checked +// and at the end there is a situation when header is already a part of blockchain +func TestPolybft_VerifyHeader(t *testing.T) { + t.Parallel() + + const ( + allValidatorsSize = 6 // overall there are 6 validators + validatorSetSize = 5 // only 5 validators are active at the time + fixedEpochSize = uint64(10) + ) + + updateHeaderExtra := func(header *types.Header, + validators *ValidatorSetDelta, + parentSignature *Signature, + checkpointData *CheckpointData, + committedAccounts []*wallet.Account) *Signature { + extra := &Extra{ + Validators: validators, + Parent: parentSignature, + Checkpoint: checkpointData, + Committed: &Signature{}, + Seal: []byte{}, + } + + if extra.Checkpoint == nil { + extra.Checkpoint = &CheckpointData{} + } + + header.ExtraData = append(make([]byte, ExtraVanity), extra.MarshalRLPTo(nil)...) + header.ComputeHash() + + if len(committedAccounts) > 0 { + checkpointHash, err := extra.Checkpoint.Hash(0, header.Number, header.Hash) + require.NoError(t, err) + + extra.Committed = createSignature(t, committedAccounts, checkpointHash) + header.ExtraData = append(make([]byte, signer.IstanbulExtraVanity), extra.MarshalRLPTo(nil)...) + } + + return extra.Committed + } + + // create all validators + validators := newTestValidators(allValidatorsSize) + + // create configuration + polyBftConfig := PolyBFTConfig{ + InitialValidatorSet: validators.getParamValidators(), + EpochSize: fixedEpochSize, + SprintSize: 5, + } + + validatorSet := validators.getPublicIdentities() + accounts := validators.getPrivateIdentities() + + // calculate validators before and after the end of the first epoch + validatorSetParent, validatorSetCurrent := validatorSet[:len(validatorSet)-1], validatorSet[1:] + accountSetParent, accountSetCurrent := accounts[:len(accounts)-1], accounts[1:] + + // create header map to simulate blockchain + headersMap := &testHeadersMap{} + + // create genesis header + genesisDelta, err := createValidatorSetDelta(nil, validatorSetParent) + require.NoError(t, err) + + genesisHeader := &types.Header{Number: 0} + updateHeaderExtra(genesisHeader, genesisDelta, nil, nil, nil) + + // add genesis header to map + headersMap.addHeader(genesisHeader) + + // create headers from 1 to 9 + for i := uint64(1); i < polyBftConfig.EpochSize; i++ { + delta, err := createValidatorSetDelta(validatorSetParent, validatorSetParent) + require.NoError(t, err) + + header := &types.Header{Number: i} + updateHeaderExtra(header, delta, nil, &CheckpointData{EpochNumber: 1}, nil) + + // add headers from 1 to 9 to map (blockchain imitation) + headersMap.addHeader(header) + } + + // mock blockchain + blockchainMock := new(blockchainMock) + blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headersMap.getHeader) + blockchainMock.On("GetHeaderByHash", mock.Anything).Return(headersMap.getHeaderByHash) + + // create polybft with appropriate mocks + polybft := &Polybft{ + closeCh: make(chan struct{}), + logger: hclog.NewNullLogger(), + consensusConfig: &polyBftConfig, + blockchain: blockchainMock, + validatorsCache: newValidatorsSnapshotCache( + hclog.NewNullLogger(), + newTestState(t), + blockchainMock, + ), + } + + // create parent header (block 10) + parentDelta, err := createValidatorSetDelta(validatorSetParent, validatorSetCurrent) + require.NoError(t, err) + + parentHeader := &types.Header{ + Number: polyBftConfig.EpochSize, + Timestamp: uint64(time.Now().UnixMilli()), + } + parentCommitment := updateHeaderExtra(parentHeader, parentDelta, nil, &CheckpointData{EpochNumber: 1}, accountSetParent) + + // add parent header to map + headersMap.addHeader(parentHeader) + + // create current header (block 11) with all appropriate fields required for validation + currentDelta, err := createValidatorSetDelta(validatorSetCurrent, validatorSetCurrent) + require.NoError(t, err) + + currentHeader := &types.Header{ + Number: polyBftConfig.EpochSize + 1, + ParentHash: parentHeader.Hash, + Timestamp: parentHeader.Timestamp + 1, + MixHash: PolyBFTMixDigest, + Difficulty: 1, + } + updateHeaderExtra(currentHeader, currentDelta, nil, + &CheckpointData{ + EpochNumber: 2, + CurrentValidatorsHash: types.StringToHash("Foo"), + NextValidatorsHash: types.StringToHash("Bar"), + }, nil) + + currentHeader.Hash[0] = currentHeader.Hash[0] + 1 + assert.ErrorContains(t, polybft.VerifyHeader(currentHeader), "invalid header hash") + + // omit Parent field (parent signature) intentionally + updateHeaderExtra(currentHeader, currentDelta, nil, + &CheckpointData{ + EpochNumber: 1, + CurrentValidatorsHash: types.StringToHash("Foo"), + NextValidatorsHash: types.StringToHash("Bar")}, + accountSetCurrent) + + // since parent signature is intentionally disregarded the following error is expected + assert.ErrorContains(t, polybft.VerifyHeader(currentHeader), "failed to verify signatures for parent of block") + + updateHeaderExtra(currentHeader, currentDelta, parentCommitment, + &CheckpointData{ + EpochNumber: 1, + CurrentValidatorsHash: types.StringToHash("Foo"), + NextValidatorsHash: types.StringToHash("Bar")}, + accountSetCurrent) + + assert.NoError(t, polybft.VerifyHeader(currentHeader)) + + // clean validator snapshot cache (re-instantiate it), submit invalid validator set for parent signature and expect the following error + polybft.validatorsCache = newValidatorsSnapshotCache(hclog.NewNullLogger(), newTestState(t), blockchainMock) + assert.NoError(t, polybft.validatorsCache.storeSnapshot(&validatorSnapshot{Epoch: 0, Snapshot: validatorSetCurrent})) // invalid validator set is submitted + assert.NoError(t, polybft.validatorsCache.storeSnapshot(&validatorSnapshot{Epoch: 1, Snapshot: validatorSetCurrent})) + assert.ErrorContains(t, polybft.VerifyHeader(currentHeader), "failed to verify signatures for parent of block") + + // clean validators cache again and set valid snapshots + polybft.validatorsCache = newValidatorsSnapshotCache(hclog.NewNullLogger(), newTestState(t), blockchainMock) + assert.NoError(t, polybft.validatorsCache.storeSnapshot(&validatorSnapshot{Epoch: 0, Snapshot: validatorSetParent})) + assert.NoError(t, polybft.validatorsCache.storeSnapshot(&validatorSnapshot{Epoch: 1, Snapshot: validatorSetCurrent})) + assert.NoError(t, polybft.VerifyHeader(currentHeader)) + + // add current header to the blockchain (headersMap) and try validating again + headersMap.addHeader(currentHeader) + assert.NoError(t, polybft.VerifyHeader(currentHeader)) +} + +func TestPolybft_Close(t *testing.T) { + syncer := &syncerMock{} + syncer.On("Close", mock.Anything).Return(error(nil)).Once() + + polybft := Polybft{ + closeCh: make(chan struct{}), + syncer: syncer, + runtime: &consensusRuntime{stateSyncManager: &dummyStateSyncManager{}}, + } + + assert.NoError(t, polybft.Close()) + + <-polybft.closeCh + + syncer.AssertExpectations(t) + + errExpected := errors.New("something") + syncer.On("Close", mock.Anything).Return(errExpected).Once() + + polybft.closeCh = make(chan struct{}) + + assert.Error(t, errExpected, polybft.Close()) + + select { + case <-polybft.closeCh: + assert.Fail(t, "channel closing is invoked") + case <-time.After(time.Millisecond * 100): + } + + syncer.AssertExpectations(t) +} + +func TestPolybft_GetSyncProgression(t *testing.T) { + result := &progress.Progression{} + + syncer := &syncerMock{} + syncer.On("GetSyncProgression", mock.Anything).Return(result).Once() + + polybft := Polybft{ + syncer: syncer, + } + + assert.Equal(t, result, polybft.GetSyncProgression()) +} + +func Test_Factory(t *testing.T) { + const epochSize = uint64(141) + + txPool := &txpool.TxPool{} + + params := &consensus.Params{ + TxPool: txPool, + Logger: hclog.Default(), + Config: &consensus.Config{ + Config: map[string]interface{}{ + "EpochSize": epochSize, + }, + }, + } + + r, err := Factory(params) + + require.NoError(t, err) + require.NotNil(t, r) + + polybft, ok := r.(*Polybft) + require.True(t, ok) + + assert.Equal(t, txPool, polybft.txPool) + assert.Equal(t, epochSize, polybft.consensusConfig.EpochSize) + assert.Equal(t, params, polybft.config) +} diff --git a/consensus/polybft/proposer_calculator.go b/consensus/polybft/proposer_calculator.go new file mode 100644 index 0000000000..e6c0d4a38c --- /dev/null +++ b/consensus/polybft/proposer_calculator.go @@ -0,0 +1,478 @@ +package polybft + +import ( + "bytes" + "fmt" + "math/big" + + "github.com/0xPolygon/polygon-edge/helper/common" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" +) + +var ( + // priorityWindowSizeFactor - is a constant that when multiplied with the + // total voting power gives the maximum allowed distance between validator + // priorities. + priorityWindowSizeFactor = big.NewInt(2) +) + +// PrioritizedValidator holds ValidatorMetadata together with priority +type PrioritizedValidator struct { + Metadata *ValidatorMetadata + ProposerPriority *big.Int +} + +// ProposerSnapshot represents snapshot of one proposer calculation +type ProposerSnapshot struct { + Height uint64 + Round uint64 + Proposer *PrioritizedValidator + Validators []*PrioritizedValidator +} + +// NewProposerSnapshotFromState create ProposerSnapshot from state if possible or from genesis block +func NewProposerSnapshotFromState(config *runtimeConfig) (*ProposerSnapshot, error) { + snapshot, err := config.State.getProposerSnapshot() + if err != nil { + return nil, err + } + + if snapshot == nil { + // pick validator set from genesis block if snapshot is not saved in db + genesisValidatorsSet, err := config.polybftBackend.GetValidators(0, nil) + if err != nil { + return nil, err + } + + snapshot = NewProposerSnapshot(1, genesisValidatorsSet) + } + + return snapshot, nil +} + +// NewProposerSnapshot creates ProposerSnapshot with height and validators with all priorities set to zero +func NewProposerSnapshot(height uint64, validators []*ValidatorMetadata) *ProposerSnapshot { + validatorsSnap := make([]*PrioritizedValidator, len(validators)) + + for i, x := range validators { + validatorsSnap[i] = &PrioritizedValidator{Metadata: x, ProposerPriority: big.NewInt(0)} + } + + return &ProposerSnapshot{ + Round: 0, + Proposer: nil, + Height: height, + Validators: validatorsSnap, + } +} + +// CalcProposer calculates next proposer +func (pcs *ProposerSnapshot) CalcProposer(round, height uint64) (types.Address, error) { + if height != pcs.Height { + return types.ZeroAddress, fmt.Errorf("invalid height - expected %d, got %d", pcs.Height, height) + } + + // optimization -> return current proposer if already calculated for this round + if pcs.Round == round && pcs.Proposer != nil { + return pcs.Proposer.Metadata.Address, nil + } + + // do not change priorities on original snapshot while executing CalcProposer + // if round = 0 then we need one iteration + proposer, err := incrementProposerPriorityNTimes(pcs.Copy(), round+1) + if err != nil { + return types.ZeroAddress, err + } + + pcs.Proposer = proposer + pcs.Round = round + + return proposer.Metadata.Address, nil +} + +// GetLatestProposer returns latest calculated proposer if any +func (pcs *ProposerSnapshot) GetLatestProposer(round, height uint64) (types.Address, error) { + // round must be same as saved one and proposer must exist + if pcs == nil || pcs.Proposer == nil || pcs.Round != round || pcs.Height != height { + return types.ZeroAddress, + fmt.Errorf("get latest proposer not found - height: %d, round: %d, pc height: %d, pc round: %d", + height, round, pcs.Height, pcs.Round) + } + + return pcs.Proposer.Metadata.Address, nil +} + +// GetTotalVotingPower returns total voting power from all the validators +func (pcs ProposerSnapshot) GetTotalVotingPower() *big.Int { + totalVotingPower := new(big.Int) + for _, v := range pcs.Validators { + totalVotingPower.Add(totalVotingPower, v.Metadata.VotingPower) + } + + return totalVotingPower +} + +// Copy Returns copy of current ProposerSnapshot object +func (pcs *ProposerSnapshot) Copy() *ProposerSnapshot { + var proposer *PrioritizedValidator + + valCopy := make([]*PrioritizedValidator, len(pcs.Validators)) + + for i, val := range pcs.Validators { + valCopy[i] = &PrioritizedValidator{Metadata: val.Metadata.Copy(), ProposerPriority: val.ProposerPriority} + + if pcs.Proposer != nil && pcs.Proposer.Metadata.Address == val.Metadata.Address { + proposer = valCopy[i] + } + } + + return &ProposerSnapshot{ + Validators: valCopy, + Height: pcs.Height, + Round: pcs.Round, + Proposer: proposer, + } +} + +func (pcs *ProposerSnapshot) toMap() map[types.Address]*PrioritizedValidator { + validatorMap := make(map[types.Address]*PrioritizedValidator) + for _, v := range pcs.Validators { + validatorMap[v.Metadata.Address] = v + } + + return validatorMap +} + +type ProposerCalculator struct { + // current snapshot + snapshot *ProposerSnapshot + + // runtime configuration + config *runtimeConfig + + // state to save snapshot + state *State + + // logger instance + logger hclog.Logger +} + +// NewProposerCalculator creates a new proposer calculator object +func NewProposerCalculator(config *runtimeConfig, logger hclog.Logger) (*ProposerCalculator, error) { + snap, err := NewProposerSnapshotFromState(config) + + if err != nil { + return nil, err + } + + return &ProposerCalculator{ + snapshot: snap, + config: config, + state: config.State, + logger: logger, + }, nil +} + +// NewProposerCalculator creates a new proposer calculator object +func NewProposerCalculatorFromSnapshot(pcs *ProposerSnapshot, config *runtimeConfig, + logger hclog.Logger) *ProposerCalculator { + return &ProposerCalculator{ + snapshot: pcs.Copy(), + config: config, + state: config.State, + logger: logger, + } +} + +// Get copy of the proposers' snapshot +func (pc *ProposerCalculator) GetSnapshot() (*ProposerSnapshot, bool) { + if pc.snapshot == nil { + return nil, false + } + + return pc.snapshot.Copy(), true +} + +// PostBlock is called on every insert of finalized block (either from consensus or syncer) +// It will update priorities and save the updated snapshot to db +func (pc *ProposerCalculator) PostBlock(req *PostBlockRequest) error { + blockNumber := req.FullBlock.Block.Number() + pc.logger.Info("Update proposal snapshot started", "block", blockNumber) + + from := pc.snapshot.Height + + // using a for loop if in some previous block, an error occurred while updating snapshot + // so that we can recalculate it to have accurate priorities + // Note, this will change once we introduce component wide global transaction + for height := from; height <= blockNumber; height++ { + if err := pc.updatePerBlock(height); err != nil { + return err + } + } + + if err := pc.state.writeProposerSnapshot(pc.snapshot); err != nil { + return fmt.Errorf("cannot save proposer calculator snapshot for block %d: %w", blockNumber, err) + } + + pc.logger.Info("Update proposal snapshot finished", "block", blockNumber) + + return nil +} + +// Updates ProposerSnapshot to block block with number `blockNumber` +func (pc *ProposerCalculator) updatePerBlock(blockNumber uint64) error { + if pc.snapshot.Height != blockNumber { + return fmt.Errorf("proposer calculator update wrong block=%d, height = %d", blockNumber, pc.snapshot.Height) + } + + _, extra, err := getBlockData(blockNumber, pc.config.blockchain) + if err != nil { + return fmt.Errorf("cannot get block header and extra while updating proposer snapshot %d: %w", blockNumber, err) + } + + var newValidatorSet AccountSet = nil + + if extra.Validators != nil && !extra.Validators.IsEmpty() { + newValidatorSet, err = pc.config.polybftBackend.GetValidators(blockNumber, nil) + if err != nil { + return fmt.Errorf("cannot get validators for block %d: %w", blockNumber, err) + } + } + + // if round = 0 then we need one iteration + _, err = incrementProposerPriorityNTimes(pc.snapshot, extra.Checkpoint.BlockRound+1) + if err != nil { + return fmt.Errorf("failed to update calculator for block %d: %w", blockNumber, err) + } + + // update to new validator set and center if needed + if err = updateValidators(pc.snapshot, newValidatorSet); err != nil { + return fmt.Errorf("cannot update validators: %w", err) + } + + pc.snapshot.Height = blockNumber + 1 // snapshot (validator priorities) is prepared for the next block + pc.snapshot.Round = 0 + pc.snapshot.Proposer = nil + + pc.logger.Info("proposer calculator update has been finished", "height", blockNumber+1, + "len", len(pc.snapshot.Validators)) + + return nil +} + +// algorithm functions receive snapshot and do appropriate calculations and changes +func incrementProposerPriorityNTimes(snapshot *ProposerSnapshot, times uint64) (*PrioritizedValidator, error) { + if len(snapshot.Validators) == 0 { + return nil, fmt.Errorf("validator set cannot be nul or empty") + } + + if times <= 0 { + return nil, fmt.Errorf("cannot call IncrementProposerPriority with non-positive times") + } + + var ( + proposer *PrioritizedValidator + err error + tvp = snapshot.GetTotalVotingPower() + ) + + if err := updateWithChangeSet(snapshot, tvp); err != nil { + return nil, err + } + + for i := uint64(0); i < times; i++ { + if proposer, err = incrementProposerPriority(snapshot, tvp); err != nil { + return nil, fmt.Errorf("cannot increment proposer priority: %w", err) + } + } + + snapshot.Proposer = proposer + snapshot.Round = times - 1 + + return proposer, nil +} + +func updateValidators(snapshot *ProposerSnapshot, newValidatorSet AccountSet) error { + if newValidatorSet.Len() == 0 { + return nil + } + + snapshotValidators := snapshot.toMap() + newValidators := make([]*PrioritizedValidator, len(newValidatorSet)) + + // compute total voting power of removed validators and current validator + removedValidatorsVotingPower := new(big.Int) + newValidatorsVotingPower := new(big.Int) + + for address, val := range snapshotValidators { + if !newValidatorSet.ContainsNodeID(address.String()) { + removedValidatorsVotingPower.Add(removedValidatorsVotingPower, val.Metadata.VotingPower) + } + } + + for _, v := range newValidatorSet { + newValidatorsVotingPower.Add(newValidatorsVotingPower, v.VotingPower) + } + + tvpAfterUpdatesBeforeRemovals := new(big.Int).Add(newValidatorsVotingPower, removedValidatorsVotingPower) + + for i, newValidator := range newValidatorSet { + if val, exists := snapshotValidators[newValidator.Address]; exists { + // old validators have the same priority + newValidators[i] = &PrioritizedValidator{ + Metadata: newValidator, + ProposerPriority: val.ProposerPriority, + } + } else { + // added validator has priority = -C*totalVotingPowerBeforeRemoval (with C ~= 1.125) + coefficient := new(big.Int).Div(tvpAfterUpdatesBeforeRemovals, big.NewInt(8)) + newValidators[i] = &PrioritizedValidator{ + Metadata: newValidator, + ProposerPriority: new(big.Int).Neg(tvpAfterUpdatesBeforeRemovals.Add(tvpAfterUpdatesBeforeRemovals, coefficient)), + } + } + } + + snapshot.Validators = newValidators + + // after validator set changes, center values around 0 and scale + if err := updateWithChangeSet(snapshot, snapshot.GetTotalVotingPower()); err != nil { + return fmt.Errorf("cannot update validator changeset: %w", err) + } + + return nil +} + +func incrementProposerPriority(snapshot *ProposerSnapshot, totalVotingPower *big.Int) (*PrioritizedValidator, error) { + for _, val := range snapshot.Validators { + // Check for overflow for sum. + newPrio := new(big.Int).Add(val.ProposerPriority, val.Metadata.VotingPower) + val.ProposerPriority = newPrio + } + // Decrement the validator with most ProposerPriority. + mostest, err := getValWithMostPriority(snapshot) + if err != nil { + return nil, fmt.Errorf("cannot get validator with most priority: %w", err) + } + + mostest.ProposerPriority.Sub(mostest.ProposerPriority, totalVotingPower) + + return mostest, nil +} + +func updateWithChangeSet(snapshot *ProposerSnapshot, totalVotingPower *big.Int) error { + if err := rescalePriorities(snapshot, totalVotingPower); err != nil { + return fmt.Errorf("cannot rescale priorities: %w", err) + } + + if err := shiftByAvgProposerPriority(snapshot); err != nil { + return fmt.Errorf("cannot shift proposer priorities: %w", err) + } + + return nil +} + +func shiftByAvgProposerPriority(snapshot *ProposerSnapshot) error { + avgProposerPriority, err := computeAvgProposerPriority(snapshot) + if err != nil { + return fmt.Errorf("cannot compute proposer priority: %w", err) + } + + for _, val := range snapshot.Validators { + val.ProposerPriority.Sub(val.ProposerPriority, avgProposerPriority) + } + + return nil +} + +func getValWithMostPriority(snapshot *ProposerSnapshot) (result *PrioritizedValidator, err error) { + if len(snapshot.Validators) == 0 { + return nil, fmt.Errorf("validators cannot be nil or empty") + } + + for _, curr := range snapshot.Validators { + // pick curr as result if it has greater priority + // or if it has same priority but "smaller" address + if isBetterProposer(curr, result) { + result = curr + } + } + + return result, nil +} + +func computeAvgProposerPriority(snapshot *ProposerSnapshot) (*big.Int, error) { + if len(snapshot.Validators) == 0 { + return nil, fmt.Errorf("validator set cannot be nul or empty") + } + + validatorsCount := big.NewInt(int64(len(snapshot.Validators))) + sum := new(big.Int) + + for _, val := range snapshot.Validators { + sum = sum.Add(sum, val.ProposerPriority) + } + + return sum.Div(sum, validatorsCount), nil +} + +// rescalePriorities rescales the priorities such that the distance between the +// maximum and minimum is smaller than `diffMax`. +func rescalePriorities(snapshot *ProposerSnapshot, totalVotingPower *big.Int) error { + if len(snapshot.Validators) == 0 { + return fmt.Errorf("validator set cannot be nul or empty") + } + + // Cap the difference between priorities to be proportional to 2*totalPower by + // re-normalizing priorities, i.e., rescale all priorities by multiplying with: + // 2*totalVotingPower/(maxPriority - minPriority) + diffMax := new(big.Int).Mul(priorityWindowSizeFactor, totalVotingPower) + + // Calculating ceil(diff/diffMax): + // Re-normalization is performed by dividing by an integer for simplicity. + // NOTE: This may make debugging priority issues easier as well. + diff := computeMaxMinPriorityDiff(snapshot.Validators) + ratio := common.BigIntDivCeil(diff, diffMax) + + if diff.Cmp(diffMax) > 0 { + for _, val := range snapshot.Validators { + val.ProposerPriority.Quo(val.ProposerPriority, ratio) + } + } + + return nil +} + +// computeMaxMinPriorityDiff computes the difference between the max and min ProposerPriority of that set. +func computeMaxMinPriorityDiff(validators []*PrioritizedValidator) *big.Int { + min, max := validators[0].ProposerPriority, validators[0].ProposerPriority + + for _, v := range validators[1:] { + if v.ProposerPriority.Cmp(min) < 0 { + min = v.ProposerPriority + } + + if v.ProposerPriority.Cmp(max) > 0 { + max = v.ProposerPriority + } + } + + diff := new(big.Int).Sub(max, min) + + if diff.Cmp(big.NewInt(0)) < 0 { + return diff.Neg(diff) + } + + return diff +} + +func isBetterProposer(a, b *PrioritizedValidator) bool { + if b == nil || a.ProposerPriority.Cmp(b.ProposerPriority) > 0 { + return true + } else if a.ProposerPriority == b.ProposerPriority { + return bytes.Compare(a.Metadata.Address.Bytes(), b.Metadata.Address.Bytes()) <= 0 + } else { + return false + } +} diff --git a/consensus/polybft/proposer_calculator_test.go b/consensus/polybft/proposer_calculator_test.go new file mode 100644 index 0000000000..fa8541448c --- /dev/null +++ b/consensus/polybft/proposer_calculator_test.go @@ -0,0 +1,710 @@ +package polybft + +import ( + "bytes" + "math/big" + "testing" + + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestProposerCalculator_SetIndex(t *testing.T) { + t.Parallel() + + validators := newTestValidatorsWithAliases([]string{"A", "B", "C", "D", "E"}, []uint64{10, 100, 1, 50, 30}) + metadata := validators.getPublicIdentities() + + vs := validators.toValidatorSet() + + snapshot := NewProposerSnapshot(1, metadata) + + // validate no changes to validator set positions + for i, v := range vs.Accounts() { + assert.Equal(t, metadata[i].Address, v.Address) + } + + proposer, err := snapshot.CalcProposer(0, 1) + require.NoError(t, err) + assert.Equal(t, proposer, metadata[1].Address) + // validate no changes to validator set positions + for i, v := range vs.Accounts() { + assert.Equal(t, metadata[i].Address, v.Address) + } +} + +func TestProposerCalculator_RegularFlow(t *testing.T) { + t.Parallel() + + validators := newTestValidatorsWithAliases([]string{"A", "B", "C", "D", "E"}, []uint64{1, 2, 3, 4, 5}) + metadata := validators.getPublicIdentities() + + snapshot := NewProposerSnapshot(0, metadata) + + currProposerAddress, err := snapshot.CalcProposer(0, 0) + require.NoError(t, err) + assert.Equal(t, metadata[4].Address, currProposerAddress) + + proposerAddressR1, err := snapshot.CalcProposer(1, 0) + require.NoError(t, err) + assert.Equal(t, metadata[3].Address, proposerAddressR1) + + proposerAddressR2, err := snapshot.CalcProposer(2, 0) + require.NoError(t, err) + assert.Equal(t, metadata[2].Address, proposerAddressR2) + + proposerAddressR3, err := snapshot.CalcProposer(3, 0) + require.NoError(t, err) + assert.Equal(t, metadata[1].Address, proposerAddressR3) + + proposerAddressR4, err := snapshot.CalcProposer(4, 0) + require.NoError(t, err) + assert.Equal(t, metadata[4].Address, proposerAddressR4) + + proposerAddressR5, err := snapshot.CalcProposer(5, 0) + require.NoError(t, err) + assert.Equal(t, metadata[3].Address, proposerAddressR5) + + proposerAddressR6, err := snapshot.CalcProposer(6, 0) + require.NoError(t, err) + assert.Equal(t, metadata[0].Address, proposerAddressR6) +} + +func TestProposerCalculator_SamePriority(t *testing.T) { + t.Parallel() + + keys, err := bls.CreateRandomBlsKeys(5) + require.NoError(t, err) + + // at some point priorities will be the same and bytes address will be compared + vs := NewValidatorSet([]*ValidatorMetadata{ + { + BlsKey: keys[0].PublicKey(), + Address: types.Address{0x1}, + VotingPower: big.NewInt(1), + }, + { + BlsKey: keys[1].PublicKey(), + Address: types.Address{0x2}, + VotingPower: big.NewInt(2), + }, + { + BlsKey: keys[2].PublicKey(), + Address: types.Address{0x3}, + VotingPower: big.NewInt(3), + }, + }, hclog.NewNullLogger()) + + snapshot := NewProposerSnapshot(0, vs.Accounts()) + + proposerR0, err := snapshot.CalcProposer(0, 0) + require.NoError(t, err) + assert.Equal(t, types.Address{0x3}, proposerR0) + + proposerR1, err := snapshot.CalcProposer(1, 0) + require.NoError(t, err) + assert.Equal(t, types.Address{0x2}, proposerR1) + + proposerR2, err := snapshot.CalcProposer(2, 0) + require.NoError(t, err) + assert.Equal(t, types.Address{0x1}, proposerR2) + + proposerR2, err = snapshot.CalcProposer(2, 0) // call again same round + require.NoError(t, err) + assert.Equal(t, types.Address{0x1}, proposerR2) +} + +func TestProposerCalculator_InversePriorityOrderWithExpectedListOfSelection(t *testing.T) { + t.Parallel() + + const numberOfIteration = 99 + + keys, err := bls.CreateRandomBlsKeys(3) + require.NoError(t, err) + + // priorities are from high to low vp in validator set + vset := NewValidatorSet([]*ValidatorMetadata{ + { + BlsKey: keys[0].PublicKey(), + Address: types.Address{0x1}, + VotingPower: big.NewInt(1000), + }, + { + BlsKey: keys[1].PublicKey(), + Address: types.Address{0x2}, + VotingPower: big.NewInt(300), + }, + { + BlsKey: keys[2].PublicKey(), + Address: types.Address{0x3}, + VotingPower: big.NewInt(330), + }, + }, hclog.NewNullLogger()) + + snapshot := NewProposerSnapshot(4, vset.Accounts()) + + var proposers = make([]types.Address, numberOfIteration) + + for i := uint64(0); i < numberOfIteration; i++ { + proposers[i], err = snapshot.CalcProposer(i, 4) + require.NoError(t, err) + } + + // list of addresses in order that should be selected + expectedValidatorAddresses := []types.Address{ + {0x1}, {0x3}, {0x1}, {0x2}, {0x1}, {0x1}, {0x3}, {0x1}, {0x2}, {0x1}, {0x1}, {0x3}, {0x1}, {0x1}, {0x2}, {0x1}, + {0x3}, {0x1}, {0x1}, {0x2}, {0x1}, {0x1}, {0x3}, {0x1}, {0x2}, {0x1}, {0x1}, {0x3}, {0x1}, {0x2}, {0x1}, {0x1}, + {0x3}, {0x1}, {0x1}, {0x2}, {0x1}, {0x3}, {0x1}, {0x1}, {0x2}, {0x1}, {0x3}, {0x1}, {0x1}, {0x2}, {0x1}, {0x3}, + {0x1}, {0x1}, {0x2}, {0x1}, {0x3}, {0x1}, {0x1}, {0x1}, {0x3}, {0x2}, {0x1}, {0x1}, {0x1}, {0x3}, {0x1}, {0x2}, + {0x1}, {0x1}, {0x3}, {0x1}, {0x2}, {0x1}, {0x1}, {0x3}, {0x1}, {0x2}, {0x1}, {0x1}, {0x3}, {0x1}, {0x2}, {0x1}, + {0x1}, {0x3}, {0x1}, {0x1}, {0x2}, {0x1}, {0x3}, {0x1}, {0x1}, {0x2}, {0x1}, {0x3}, {0x1}, {0x1}, {0x2}, {0x1}, + {0x3}, {0x1}, {0x1}, + } + + for i, p := range proposers { + assert.True(t, bytes.Equal(expectedValidatorAddresses[i].Bytes(), p.Bytes())) + } +} + +func TestProposerCalculator_IncrementProposerPrioritySameVotingPower(t *testing.T) { + t.Parallel() + + keys, err := bls.CreateRandomBlsKeys(3) + require.NoError(t, err) + + vs := NewValidatorSet([]*ValidatorMetadata{ + { + BlsKey: keys[0].PublicKey(), + Address: types.Address{0x1}, + VotingPower: big.NewInt(1), + }, + { + BlsKey: keys[1].PublicKey(), + Address: types.Address{0x2}, + VotingPower: big.NewInt(1), + }, + { + BlsKey: keys[2].PublicKey(), + Address: types.Address{0x3}, + VotingPower: big.NewInt(1), + }, + }, hclog.NewNullLogger()) + + snapshot := NewProposerSnapshot(0, vs.Accounts()) + + // when voting power is the same order is by address + currProposerAddress, err := snapshot.CalcProposer(0, 0) + require.NoError(t, err) + assert.Equal(t, types.Address{0x1}, currProposerAddress) + + proposerAddresR1, err := snapshot.CalcProposer(1, 0) + require.NoError(t, err) + assert.Equal(t, types.Address{0x2}, proposerAddresR1) + + proposerAddressR2, err := snapshot.CalcProposer(2, 0) + require.NoError(t, err) + assert.Equal(t, types.Address{0x3}, proposerAddressR2) + + proposerAddressR3, err := snapshot.CalcProposer(3, 0) + require.NoError(t, err) + assert.Equal(t, types.Address{0x1}, proposerAddressR3) + + proposerAddressR4, err := snapshot.CalcProposer(4, 0) + require.NoError(t, err) + assert.Equal(t, types.Address{0x2}, proposerAddressR4) +} + +func TestProposerCalculator_AveragingInIncrementProposerPriorityWithVotingPower(t *testing.T) { + t.Parallel() + + keys, err := bls.CreateRandomBlsKeys(3) + require.NoError(t, err) + + // Other than TestAveragingInIncrementProposerPriority this is a more complete test showing + // how each ProposerPriority changes in relation to the validator's voting power respectively. + // average is zero in each round: + vp0 := int64(10) + vp1 := int64(1) + vp2 := int64(1) + total := vp0 + vp1 + vp2 + avg := (vp0 + vp1 + vp2 - total) / 3 + valz := []*ValidatorMetadata{ + { + BlsKey: keys[0].PublicKey(), + Address: types.Address{0x1}, + VotingPower: big.NewInt(vp0), + }, + { + BlsKey: keys[1].PublicKey(), + Address: types.Address{0x2}, + VotingPower: big.NewInt(vp1), + }, + { + BlsKey: keys[2].PublicKey(), + Address: types.Address{0x3}, + VotingPower: big.NewInt(vp2), + }, + } + + tcs := []struct { + wantProposerPriority []int64 + times uint64 + wantProposerIndex int64 + }{ + + 0: { + []int64{ + // Acumm+VotingPower-Avg: + 0 + vp0 - total - avg, // mostest will be subtracted by total voting power (12) + 0 + vp1, + 0 + vp2}, + 1, + 0, + }, + 1: { + []int64{ + (0 + vp0 - total) + vp0 - total - avg, // this will be mostest on 2nd iter, too + (0 + vp1) + vp1, + (0 + vp2) + vp2}, + 2, + 0, + }, // increment twice -> expect average to be subtracted twice + 2: { + []int64{ + 0 + 3*(vp0-total) - avg, // still mostest + 0 + 3*vp1, + 0 + 3*vp2}, + 3, + 0, + }, + 3: { + []int64{ + 0 + 4*(vp0-total), // still mostest + 0 + 4*vp1, + 0 + 4*vp2}, + 4, + 0, + }, + 4: { + []int64{ + 0 + 4*(vp0-total) + vp0, // 4 iters was mostest + 0 + 5*vp1 - total, // now this val is mostest for the 1st time (hence -12==totalVotingPower) + 0 + 5*vp2}, + 5, + 1, + }, + 5: { + []int64{ + 0 + 6*vp0 - 5*total, // mostest again + 0 + 6*vp1 - total, // mostest once up to here + 0 + 6*vp2}, + 6, + 0, + }, + 6: { + []int64{ + 0 + 7*vp0 - 6*total, // in 7 iters this val is mostest 6 times + 0 + 7*vp1 - total, // in 7 iters this val is mostest 1 time + 0 + 7*vp2}, + 7, + 0, + }, + 7: { + []int64{ + 0 + 8*vp0 - 7*total, // mostest again + 0 + 8*vp1 - total, + 0 + 8*vp2}, + 8, + 0, + }, + 8: { + []int64{ + 0 + 9*vp0 - 7*total, + 0 + 9*vp1 - total, + 0 + 9*vp2 - total}, // mostest + 9, + 2, + }, + 9: { + []int64{ + 0 + 10*vp0 - 8*total, // after 10 iters this is mostest again + 0 + 10*vp1 - total, // after 6 iters this val is "mostest" once and not in between + 0 + 10*vp2 - total}, // in between 10 iters this val is "mostest" once + 10, + 0, + }, + 10: { + []int64{ + 0 + 11*vp0 - 9*total, + 0 + 11*vp1 - total, // after 6 iters this val is "mostest" once and not in between + 0 + 11*vp2 - total}, // after 10 iters this val is "mostest" once + 11, + 0, + }, + } + + for i, tc := range tcs { + snap := NewProposerSnapshot(1, valz) + + _, err := incrementProposerPriorityNTimes(snap, tc.times) + require.NoError(t, err) + + address, _ := snap.GetLatestProposer(tc.times-1, 1) + + assert.Equal(t, snap.Validators[tc.wantProposerIndex].Metadata.Address, address, + "test case: %v", + i) + + for valIdx, val := range snap.Validators { + assert.Equal(t, + tc.wantProposerPriority[valIdx], + val.ProposerPriority.Int64(), + "test case: %v, validator: %v", + i, + valIdx) + } + } +} + +func TestProposerCalculator_UpdatesForNewValidatorSet(t *testing.T) { + t.Parallel() + + keys, err := bls.CreateRandomBlsKeys(2) + require.NoError(t, err) + + v1 := &ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(100)} + v2 := &ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(100)} + + accountSet := []*ValidatorMetadata{v1, v2} + vs := NewValidatorSet(accountSet, hclog.NewNullLogger()) + + snapshot := NewProposerSnapshot(0, vs.Accounts()) + + _, err = snapshot.CalcProposer(1, 0) + require.NoError(t, err) + + // verify that the capacity and length of validators is the same + assert.Equal(t, len(vs.Accounts()), cap(snapshot.Validators)) + // verify that validator priorities are centered + valsCount := int64(len(snapshot.Validators)) + + sum := big.NewInt(0) + for _, val := range snapshot.Validators { + // mind overflow + sum = new(big.Int).Add(sum, val.ProposerPriority) + } + + assert.True(t, sum.Cmp(big.NewInt(valsCount)) < 0 && sum.Cmp(big.NewInt(-valsCount)) > 0, + "expected total priority in (-%d, %d). Got %d", valsCount, valsCount, sum) + + // verify that priorities are scaled + diff := computeMaxMinPriorityDiff(snapshot.Validators) + diffMax := new(big.Int).Mul(priorityWindowSizeFactor, vs.totalVotingPower) + assert.True(t, diff.Cmp(diffMax) <= 0, "expected priority distance < %d. Got %d", diffMax, diff) +} + +func TestProposerCalculator_GetLatestProposer(t *testing.T) { + t.Parallel() + + const ( + bestIdx = 5 + count = 10 + ) + + validatorSet := newTestValidators(count).getPublicIdentities() + snapshot := NewProposerSnapshot(0, validatorSet) + snapshot.Validators[bestIdx].ProposerPriority = big.NewInt(1000000) + + // not set + _, err := snapshot.GetLatestProposer(0, 0) + assert.Error(t, err) + + address, err := snapshot.CalcProposer(0, 0) + assert.NoError(t, err) + + // wrong round + _, err = snapshot.GetLatestProposer(1, 0) + assert.Error(t, err) + + // wrong height + _, err = snapshot.GetLatestProposer(0, 1) + assert.Error(t, err) + + // ok + address, err = snapshot.GetLatestProposer(0, 0) + assert.NoError(t, err) + + proposerAddress := validatorSet[bestIdx].Address + assert.Equal(t, proposerAddress, address) +} + +func TestProposerCalculator_UpdateValidatorsSameVpUpdatedAndNewAdded(t *testing.T) { + t.Parallel() + + keys, err := bls.CreateRandomBlsKeys(8) + require.NoError(t, err) + + v1 := &ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(100)} + v2 := &ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(100)} + v3 := &ValidatorMetadata{Address: types.Address{0x3}, BlsKey: keys[2].PublicKey(), VotingPower: big.NewInt(100)} + v4 := &ValidatorMetadata{Address: types.Address{0x4}, BlsKey: keys[3].PublicKey(), VotingPower: big.NewInt(100)} + v5 := &ValidatorMetadata{Address: types.Address{0x5}, BlsKey: keys[4].PublicKey(), VotingPower: big.NewInt(100)} + + vs := NewValidatorSet([]*ValidatorMetadata{v1, v2, v3, v4, v5}, hclog.NewNullLogger()) + + snapshot := NewProposerSnapshot(0, vs.Accounts()) + + // iterate one cycle should bring back priority to 0 + _, err = incrementProposerPriorityNTimes(snapshot, 5) + require.NoError(t, err) + + for _, v := range snapshot.Validators { + assert.True(t, v.ProposerPriority.Cmp(big.NewInt(0)) == 0) + } + + // updated old validators + u1 := &ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(10)} + u2 := &ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[2].PublicKey(), VotingPower: big.NewInt(10)} + // added new validator + a1 := &ValidatorMetadata{Address: types.Address{0x9}, BlsKey: keys[7].PublicKey(), VotingPower: big.NewInt(100)} + + newAccountSet := []*ValidatorMetadata{u1, u2, a1} + + updateValidators(snapshot, newAccountSet) + assert.Equal(t, 3, len(snapshot.Validators)) + + // removedVp := sum(v3, v4, v5) = 300 + // newVp := sum(u1, u2, a1) = 120 + // sum(removedVp, newVp) = 420; priority(a1) = -1.125*420 = -472 + // scale: difMax = 2 * 120; diff(-475, 0); ratio ~ 2 + // priority(a1) = -472/2 = 236; u1 = 0, u2 = 0 + // center: avg = 236/3 = 79; priority(a1)= 236 - 79 + + // check voting power after update + assert.Equal(t, big.NewInt(10), snapshot.Validators[0].Metadata.VotingPower) + assert.Equal(t, big.NewInt(10), snapshot.Validators[1].Metadata.VotingPower) + assert.Equal(t, big.NewInt(100), snapshot.Validators[2].Metadata.VotingPower) + // newly added validator + assert.Equal(t, big.NewInt(100), snapshot.Validators[2].Metadata.VotingPower) + assert.Equal(t, types.Address{0x9}, snapshot.Validators[2].Metadata.Address) + assert.Equal(t, big.NewInt(-157), snapshot.Validators[2].ProposerPriority) // a1 + // check priority + assert.Equal(t, big.NewInt(79), snapshot.Validators[0].ProposerPriority) // u1 + assert.Equal(t, big.NewInt(79), snapshot.Validators[1].ProposerPriority) // u2 + + _, err = incrementProposerPriorityNTimes(snapshot, 1) + require.NoError(t, err) + + // 79 + 10 - (100+10+10) + assert.Equal(t, big.NewInt(-31), snapshot.Validators[0].ProposerPriority) + // 79 + 10 + assert.Equal(t, big.NewInt(89), snapshot.Validators[1].ProposerPriority) + // -157+100 + assert.Equal(t, big.NewInt(-57), snapshot.Validators[2].ProposerPriority) +} + +func TestProposerCalculator_UpdateValidators(t *testing.T) { + t.Parallel() + + keys, err := bls.CreateRandomBlsKeys(4) + require.NoError(t, err) + + v1 := &ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(10)} + v2 := &ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(20)} + v3 := &ValidatorMetadata{Address: types.Address{0x3}, BlsKey: keys[2].PublicKey(), VotingPower: big.NewInt(30)} + + vs := NewValidatorSet([]*ValidatorMetadata{v1, v2, v3}, hclog.NewNullLogger()) + + snapshot := NewProposerSnapshot(0, vs.Accounts()) + require.Equal(t, big.NewInt(60), snapshot.GetTotalVotingPower()) + // init priority must be 0 + require.Zero(t, snapshot.Validators[0].ProposerPriority.Int64()) + require.Zero(t, snapshot.Validators[1].ProposerPriority.Int64()) + require.Zero(t, snapshot.Validators[2].ProposerPriority.Int64()) + // vp must be initialized + require.Equal(t, big.NewInt(10), snapshot.Validators[0].Metadata.VotingPower) + require.Equal(t, big.NewInt(20), snapshot.Validators[1].Metadata.VotingPower) + require.Equal(t, big.NewInt(30), snapshot.Validators[2].Metadata.VotingPower) + + // increment once + _, err = incrementProposerPriorityNTimes(snapshot, 1) + require.NoError(t, err) + + // updated + u1 := &ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(100)} + u2 := &ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(200)} + u3 := &ValidatorMetadata{Address: types.Address{0x3}, BlsKey: keys[2].PublicKey(), VotingPower: big.NewInt(300)} + // added + a1 := &ValidatorMetadata{Address: types.Address{0x4}, BlsKey: keys[3].PublicKey(), VotingPower: big.NewInt(400)} + + updateValidators(snapshot, []*ValidatorMetadata{u1, u2, u3, a1}) + + require.Equal(t, 4, len(snapshot.Validators)) + // priorities are from previous iteration + require.Equal(t, big.NewInt(292), snapshot.Validators[0].ProposerPriority) + require.Equal(t, big.NewInt(302), snapshot.Validators[1].ProposerPriority) + require.Equal(t, big.NewInt(252), snapshot.Validators[2].ProposerPriority) + // new added a1 + require.Equal(t, types.Address{0x4}, snapshot.Validators[3].Metadata.Address) + require.Equal(t, big.NewInt(-843), snapshot.Validators[3].ProposerPriority) + // total vp is updated + require.Equal(t, big.NewInt(1000), snapshot.GetTotalVotingPower()) +} + +func TestProposerCalculator_ScaleAfterDelete(t *testing.T) { + t.Parallel() + + keys, err := bls.CreateRandomBlsKeys(3) + require.NoError(t, err) + + v1 := &ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(10)} + v2 := &ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(10)} + v3 := &ValidatorMetadata{Address: types.Address{0x3}, BlsKey: keys[2].PublicKey(), VotingPower: big.NewInt(80000)} + + vs := NewValidatorSet([]*ValidatorMetadata{v1, v2, v3}, hclog.NewNullLogger()) + + snapshot := NewProposerSnapshot(0, vs.Accounts()) + assert.Equal(t, big.NewInt(80020), snapshot.GetTotalVotingPower()) + + _, err = incrementProposerPriorityNTimes(snapshot, 1) + require.NoError(t, err) + + // priorities are from previous iteration + require.Equal(t, big.NewInt(10), snapshot.Validators[0].ProposerPriority) + require.Equal(t, big.NewInt(10), snapshot.Validators[1].ProposerPriority) + require.Equal(t, big.NewInt(-20), snapshot.Validators[2].ProposerPriority) + + // another increment + proposer, err := incrementProposerPriorityNTimes(snapshot, 4000) + require.NoError(t, err) + // priorities are from previous iteration + assert.Equal(t, types.Address{0x3}, proposer.Metadata.Address) + + // reduce validator voting power from 8k to 1 + u1 := &ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(10)} + u2 := &ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(10)} + + require.Equal(t, big.NewInt(-40010), snapshot.Validators[0].ProposerPriority) + require.Equal(t, big.NewInt(40010), snapshot.Validators[1].ProposerPriority) + + updateValidators(snapshot, []*ValidatorMetadata{u1, u2}) + + // maxdiff = 2*tvp = 40 + // diff(min,max) (-40010, 40010) = 80020 + // ratio := (diff + diffMax - 1) / diffMax; (80020 + 20 - 1)/20 = 2001 + // priority = priority / ratio; u1 = -40010 / 4001 ~ -19; u2 = 40010 / 4001 ~ 19 + require.Equal(t, big.NewInt(-19), snapshot.Validators[0].ProposerPriority) + require.Equal(t, big.NewInt(19), snapshot.Validators[1].ProposerPriority) + require.Equal(t, big.NewInt(20), snapshot.GetTotalVotingPower()) +} + +func TestProposerCalculator_ShiftAfterUpdate(t *testing.T) { + t.Parallel() + + keys, err := bls.CreateRandomBlsKeys(3) + require.NoError(t, err) + + v1 := &ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(50)} + v2 := &ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(80)} + v3 := &ValidatorMetadata{Address: types.Address{0x3}, BlsKey: keys[2].PublicKey(), VotingPower: big.NewInt(100000)} + + vs := NewValidatorSet([]*ValidatorMetadata{v1, v2, v3}, hclog.NewNullLogger()) + + snapshot := NewProposerSnapshot(0, vs.Accounts()) + assert.Equal(t, big.NewInt(100130), snapshot.GetTotalVotingPower()) + + _, err = incrementProposerPriorityNTimes(snapshot, 4000) + require.NoError(t, err) + + // updates + u1 := &ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(5)} + u2 := &ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(8)} + + updateValidators(snapshot, []*ValidatorMetadata{u1, u2}) + + // maxdiff = 2*tvp = 26 + // diff(min,max) (-260, 19610) = 19870 + // ratio := (diff + diffMax - 1) / diffMax; (19870 + 26 - 1)/26 =765 + // scale priority = priority / ratio; p1 = 0; p2 = 25 + // shift with avg=(25+0)/2=12; p = priority - avg; u1 = -12; u2= 13 + require.Equal(t, big.NewInt(-12), snapshot.Validators[0].ProposerPriority) + require.Equal(t, big.NewInt(13), snapshot.Validators[1].ProposerPriority) + require.Equal(t, big.NewInt(13), snapshot.GetTotalVotingPower()) +} + +func TestProposerCalculator_UpdateValidatorSet(t *testing.T) { + t.Parallel() + + keys, err := bls.CreateRandomBlsKeys(3) + require.NoError(t, err) + + v1 := &ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(1)} + v2 := &ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(8)} + v3 := &ValidatorMetadata{Address: types.Address{0x3}, BlsKey: keys[2].PublicKey(), VotingPower: big.NewInt(15)} + + vs := NewValidatorSet([]*ValidatorMetadata{v1, v2, v3}, hclog.NewNullLogger()) + + snapshot := NewProposerSnapshot(0, vs.Accounts()) + assert.Equal(t, big.NewInt(24), snapshot.GetTotalVotingPower()) + + _, err = incrementProposerPriorityNTimes(snapshot, 2) + require.NoError(t, err) + + // modified validator + u1 := &ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(5)} + // added validator + a1 := &ValidatorMetadata{Address: types.Address{0x4}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(8)} + + updateValidators(snapshot, []*ValidatorMetadata{u1, a1}) + // expecting 2 validators with updated voting power and total voting power + require.Equal(t, 2, len(snapshot.Validators)) + require.Equal(t, types.Address{0x1}, snapshot.Validators[0].Metadata.Address) + require.Equal(t, big.NewInt(5), snapshot.Validators[0].Metadata.VotingPower) + require.Equal(t, big.NewInt(11), snapshot.Validators[0].ProposerPriority) + + require.Equal(t, types.Address{0x4}, snapshot.Validators[1].Metadata.Address) + require.Equal(t, big.NewInt(8), snapshot.Validators[1].Metadata.VotingPower) + require.Equal(t, big.NewInt(-10), snapshot.Validators[1].ProposerPriority) + require.Equal(t, big.NewInt(13), snapshot.GetTotalVotingPower()) +} + +func TestProposerCalculator_AddValidator(t *testing.T) { + t.Parallel() + + keys, err := bls.CreateRandomBlsKeys(3) + require.NoError(t, err) + + v1 := &ValidatorMetadata{Address: types.Address{0x1}, BlsKey: keys[0].PublicKey(), VotingPower: big.NewInt(3)} + v2 := &ValidatorMetadata{Address: types.Address{0x2}, BlsKey: keys[1].PublicKey(), VotingPower: big.NewInt(1)} + + vs := NewValidatorSet([]*ValidatorMetadata{v1, v2}, hclog.NewNullLogger()) + + snapshot := NewProposerSnapshot(0, vs.Accounts()) + assert.Equal(t, big.NewInt(4), snapshot.GetTotalVotingPower()) + proposer, err := incrementProposerPriorityNTimes(snapshot, 1) + require.NoError(t, err) + require.Equal(t, types.Address{0x1}, proposer.Metadata.Address) + require.Equal(t, big.NewInt(-1), snapshot.Validators[0].ProposerPriority) + require.Equal(t, big.NewInt(1), snapshot.Validators[1].ProposerPriority) + + _, err = incrementProposerPriorityNTimes(snapshot, 1) + require.NoError(t, err) + + require.Equal(t, big.NewInt(-2), snapshot.Validators[0].ProposerPriority) + require.Equal(t, big.NewInt(2), snapshot.Validators[1].ProposerPriority) + + a1 := &ValidatorMetadata{Address: types.Address{0x3}, BlsKey: keys[2].PublicKey(), VotingPower: big.NewInt(8)} + + updateValidators(snapshot, []*ValidatorMetadata{v1, v2, a1}) + + // updated vp: 8+3+1 = 12 + // added validator priority = -1.125*8 ~ -13 + // scaling: max(-13, 3) = 16 < 2* 12; no scaling + // centring: avg = (13+3+1)/3=5; v1=-2+5, v2=2+5; u3=-13+5 + require.Equal(t, big.NewInt(3), snapshot.Validators[0].ProposerPriority) + require.Equal(t, big.NewInt(7), snapshot.Validators[1].ProposerPriority) + require.Equal(t, big.NewInt(-8), snapshot.Validators[2].ProposerPriority) +} diff --git a/consensus/polybft/proto/transport.pb.go b/consensus/polybft/proto/transport.pb.go new file mode 100644 index 0000000000..3aaa60f979 --- /dev/null +++ b/consensus/polybft/proto/transport.pb.go @@ -0,0 +1,144 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.21.7 +// source: consensus/polybft/proto/transport.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type TransportMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *TransportMessage) Reset() { + *x = TransportMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_consensus_polybft_proto_transport_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TransportMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransportMessage) ProtoMessage() {} + +func (x *TransportMessage) ProtoReflect() protoreflect.Message { + mi := &file_consensus_polybft_proto_transport_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransportMessage.ProtoReflect.Descriptor instead. +func (*TransportMessage) Descriptor() ([]byte, []int) { + return file_consensus_polybft_proto_transport_proto_rawDescGZIP(), []int{0} +} + +func (x *TransportMessage) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +var File_consensus_polybft_proto_transport_proto protoreflect.FileDescriptor + +var file_consensus_polybft_proto_transport_proto_rawDesc = []byte{ + 0x0a, 0x27, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2f, 0x70, 0x6f, 0x6c, 0x79, + 0x62, 0x66, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, + 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x76, 0x31, 0x22, 0x26, 0x0a, + 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x42, 0x1a, 0x5a, 0x18, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, + 0x73, 0x75, 0x73, 0x2f, 0x70, 0x6f, 0x6c, 0x79, 0x62, 0x66, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_consensus_polybft_proto_transport_proto_rawDescOnce sync.Once + file_consensus_polybft_proto_transport_proto_rawDescData = file_consensus_polybft_proto_transport_proto_rawDesc +) + +func file_consensus_polybft_proto_transport_proto_rawDescGZIP() []byte { + file_consensus_polybft_proto_transport_proto_rawDescOnce.Do(func() { + file_consensus_polybft_proto_transport_proto_rawDescData = protoimpl.X.CompressGZIP(file_consensus_polybft_proto_transport_proto_rawDescData) + }) + return file_consensus_polybft_proto_transport_proto_rawDescData +} + +var file_consensus_polybft_proto_transport_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_consensus_polybft_proto_transport_proto_goTypes = []interface{}{ + (*TransportMessage)(nil), // 0: v1.TransportMessage +} +var file_consensus_polybft_proto_transport_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_consensus_polybft_proto_transport_proto_init() } +func file_consensus_polybft_proto_transport_proto_init() { + if File_consensus_polybft_proto_transport_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_consensus_polybft_proto_transport_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransportMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_consensus_polybft_proto_transport_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_consensus_polybft_proto_transport_proto_goTypes, + DependencyIndexes: file_consensus_polybft_proto_transport_proto_depIdxs, + MessageInfos: file_consensus_polybft_proto_transport_proto_msgTypes, + }.Build() + File_consensus_polybft_proto_transport_proto = out.File + file_consensus_polybft_proto_transport_proto_rawDesc = nil + file_consensus_polybft_proto_transport_proto_goTypes = nil + file_consensus_polybft_proto_transport_proto_depIdxs = nil +} diff --git a/consensus/polybft/proto/transport.proto b/consensus/polybft/proto/transport.proto new file mode 100644 index 0000000000..329c105cb0 --- /dev/null +++ b/consensus/polybft/proto/transport.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package v1; + +option go_package = "/consensus/polybft/proto"; + +message TransportMessage { + bytes data = 1; +} \ No newline at end of file diff --git a/consensus/polybft/runtime_helpers.go b/consensus/polybft/runtime_helpers.go new file mode 100644 index 0000000000..f2ffa2a49a --- /dev/null +++ b/consensus/polybft/runtime_helpers.go @@ -0,0 +1,45 @@ +package polybft + +import ( + "github.com/0xPolygon/polygon-edge/blockchain" + "github.com/0xPolygon/polygon-edge/types" +) + +// isEndOfPeriod checks if an end of a period (either it be sprint or epoch) +// is reached with the current block (the parent block of the current fsm iteration) +func isEndOfPeriod(blockNumber, periodSize uint64) bool { + return blockNumber%periodSize == 0 +} + +// getBlockData returns block header and extra +func getBlockData(blockNumber uint64, blockchainBackend blockchainBackend) (*types.Header, *Extra, error) { + blockHeader, found := blockchainBackend.GetHeaderByNumber(blockNumber) + if !found { + return nil, nil, blockchain.ErrNoBlock + } + + blockExtra, err := GetIbftExtra(blockHeader.ExtraData) + if err != nil { + return nil, nil, err + } + + return blockHeader, blockExtra, nil +} + +// isEpochEndingBlock checks if given block is an epoch ending block +func isEpochEndingBlock(blockNumber uint64, extra *Extra, blockchain blockchainBackend) (bool, error) { + if !extra.Validators.IsEmpty() { + // if validator set delta is not empty, the validator set was changed in this block + // meaning the epoch changed as well + return true, nil + } + + _, nextBlockExtra, err := getBlockData(blockNumber+1, blockchain) + if err != nil { + return false, err + } + + // validator set delta can be empty (no change in validator set happened) + // so we need to check if their epoch numbers are different + return extra.Checkpoint.EpochNumber != nextBlockExtra.Checkpoint.EpochNumber, nil +} diff --git a/consensus/polybft/runtime_helpers_test.go b/consensus/polybft/runtime_helpers_test.go new file mode 100644 index 0000000000..1806097cf8 --- /dev/null +++ b/consensus/polybft/runtime_helpers_test.go @@ -0,0 +1,87 @@ +package polybft + +import ( + "testing" + + "github.com/0xPolygon/polygon-edge/blockchain" + "github.com/0xPolygon/polygon-edge/consensus/polybft/bitmap" + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestHelpers_isEpochEndingBlock_DeltaNotEmpty(t *testing.T) { + t.Parallel() + + validators := newTestValidators(3).getPublicIdentities() + bitmap := bitmap.Bitmap{} + bitmap.Set(0) + + delta := &ValidatorSetDelta{ + Added: validators[1:], + Removed: bitmap, + } + + extra := &Extra{Validators: delta, Checkpoint: &CheckpointData{EpochNumber: 2}} + blockNumber := uint64(20) + + isEndOfEpoch, err := isEpochEndingBlock(blockNumber, extra, new(blockchainMock)) + require.NoError(t, err) + require.True(t, isEndOfEpoch) +} + +func TestHelpers_isEpochEndingBlock_NoBlock(t *testing.T) { + t.Parallel() + + blockchainMock := new(blockchainMock) + blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(&types.Header{}, false) + + extra := &Extra{Checkpoint: &CheckpointData{EpochNumber: 2}, Validators: &ValidatorSetDelta{}} + blockNumber := uint64(20) + + isEndOfEpoch, err := isEpochEndingBlock(blockNumber, extra, blockchainMock) + require.ErrorIs(t, blockchain.ErrNoBlock, err) + require.False(t, isEndOfEpoch) +} + +func TestHelpers_isEpochEndingBlock_EpochsNotTheSame(t *testing.T) { + t.Parallel() + + blockchainMock := new(blockchainMock) + + nextBlockExtra := &Extra{Checkpoint: &CheckpointData{EpochNumber: 3}, Validators: &ValidatorSetDelta{}} + nextBlock := &types.Header{ + Number: 21, + ExtraData: append(make([]byte, ExtraVanity), nextBlockExtra.MarshalRLPTo(nil)...), + } + + blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(nextBlock, true) + + extra := &Extra{Checkpoint: &CheckpointData{EpochNumber: 2}, Validators: &ValidatorSetDelta{}} + blockNumber := uint64(20) + + isEndOfEpoch, err := isEpochEndingBlock(blockNumber, extra, blockchainMock) + require.NoError(t, err) + require.True(t, isEndOfEpoch) +} + +func TestHelpers_isEpochEndingBlock_EpochsAreTheSame(t *testing.T) { + t.Parallel() + + blockchainMock := new(blockchainMock) + + nextBlockExtra := &Extra{Checkpoint: &CheckpointData{EpochNumber: 2}, Validators: &ValidatorSetDelta{}} + nextBlock := &types.Header{ + Number: 16, + ExtraData: append(make([]byte, ExtraVanity), nextBlockExtra.MarshalRLPTo(nil)...), + } + + blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(nextBlock, true) + + extra := &Extra{Checkpoint: &CheckpointData{EpochNumber: 2}, Validators: &ValidatorSetDelta{}} + blockNumber := uint64(15) + + isEndOfEpoch, err := isEpochEndingBlock(blockNumber, extra, blockchainMock) + require.NoError(t, err) + require.False(t, isEndOfEpoch) +} diff --git a/consensus/polybft/signer/common.go b/consensus/polybft/signer/common.go new file mode 100644 index 0000000000..4300ff27f5 --- /dev/null +++ b/consensus/polybft/signer/common.go @@ -0,0 +1,55 @@ +package bls + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "io" + "math/big" + + bn256 "github.com/umbracle/go-eth-bn256" +) + +var errInfinityPoint = fmt.Errorf("infinity point") + +var ( + // negated g2 point + negG2Point = mustG2Point("198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d") //nolint + + // g2 point + g2Point = mustG2Point("198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa") //nolint + + // domain used to map hash to G1 + domain, _ = hex.DecodeString("508e30424791cb9a71683381558c3da1979b6fa423b2d6db1396b1d94d7c4a78") +) + +func mustG2Point(str string) *bn256.G2 { + buf, err := hex.DecodeString(str) + if err != nil { + panic(err) + } + + b := new(bn256.G2) + + if _, err := b.Unmarshal(buf); err != nil { + panic(err) + } + + return b +} + +// Returns bls/bn254 domain +func GetDomain() []byte { + return domain +} + +func randomK(r io.Reader) (k *big.Int, err error) { + for { + k, err = rand.Int(r, bn256.Order) + if k.Sign() > 0 || err != nil { + // The key cannot ever be zero, otherwise the cryptographic properties + // of the curve do not hold. + return + } + } +} diff --git a/consensus/polybft/signer/hash2point.go b/consensus/polybft/signer/hash2point.go new file mode 100644 index 0000000000..ab186ec798 --- /dev/null +++ b/consensus/polybft/signer/hash2point.go @@ -0,0 +1,301 @@ +package bls + +import ( + "crypto/sha256" + "errors" + "math/big" + + "github.com/0xPolygon/polygon-edge/helper/common" + bn256 "github.com/umbracle/go-eth-bn256" +) + +var ( + z0, _ = new(big.Int).SetString("0000000000000000b3c4d79d41a91759a9e4c7e359b6b89eaec68e62effffffd", 16) + + z1, _ = new(big.Int).SetString("000000000000000059e26bcea0d48bacd4f263f1acdb5c4f5763473177fffffe", 16) + + // pPrime is a prime over which we form a basic field: 36u⁴+36u³+24u²+6u+1. + pPrime, _ = new(big.Int).SetString("21888242871839275222246405745257275088696311157297823662689037894645226208583", 10) + + modulus, _ = new(big.Int).SetString("30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", 16) + + zero = new(big.Int).SetInt64(0) +) + +// hashToPoint maps hash message and domain to G1 point +// Based on https://github.com/thehubbleproject/ and kilic library +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-07 +func hashToPoint(msg, domain []byte) (*bn256.G1, error) { + res, err := hashToFpXMDSHA256(msg, domain, 2) + if err != nil { + return nil, err + } + + //a = a.Mod(a, modulus) + //b = b.Mod(b, modulus) + a, b := res[0], res[1] + + g1, err := mapToG1Point(a) + if err != nil { + return nil, err + } + + g2, err := mapToG1Point(b) + if err != nil { + return nil, err + } + + g1.Add(g1, g2) + + return g1, nil +} + +func mapToG1Point(b *big.Int) (*bn256.G1, error) { + xx, yy, err := mapToPoint(b) + if err != nil { + return nil, err + } + + pointBytes := [64]byte{} + copy(pointBytes[:], common.PadLeftOrTrim(xx.Bytes(), 32)) + copy(pointBytes[32:], common.PadLeftOrTrim(yy.Bytes(), 32)) + + g1 := new(bn256.G1) + + if _, err := g1.Unmarshal(pointBytes[:]); err != nil { + return nil, err + } + + return g1, nil +} + +func hashToFpXMDSHA256(msg []byte, domain []byte, count int) ([]*big.Int, error) { + randBytes, err := expandMsgSHA256XMD(msg, domain, count*48) + if err != nil { + return nil, err + } + + els := make([]*big.Int, count) + + for i := 0; i < count; i++ { + num := new(big.Int).SetBytes(randBytes[i*48 : (i+1)*48]) + + // fast path + c := num.Cmp(modulus) + if c == 0 { + // nothing + } else if c != 1 && num.Cmp(zero) != -1 { + // 0 < v < q + } else { + num = num.Mod(num, modulus) + } + + // copy input + modular reduction + els[i] = num + } + + return els, nil +} + +func expandMsgSHA256XMD(msg []byte, domain []byte, outLen int) ([]byte, error) { + if len(domain) > 255 { + return nil, errors.New("invalid domain length") + } + + h := sha256.New() + + domainLen := uint8(len(domain)) + // DST_prime = DST || I2OSP(len(DST), 1) + // b_0 = H(Z_pad || msg || l_i_b_str || I2OSP(0, 1) || DST_prime) + _, _ = h.Write(make([]byte, h.BlockSize())) + _, _ = h.Write(msg) + _, _ = h.Write([]byte{uint8(outLen >> 8), uint8(outLen)}) + _, _ = h.Write([]byte{0}) + _, _ = h.Write(domain) + _, _ = h.Write([]byte{domainLen}) + b0 := h.Sum(nil) + + // b_1 = H(b_0 || I2OSP(1, 1) || DST_prime) + h.Reset() + _, _ = h.Write(b0) + _, _ = h.Write([]byte{1}) + _, _ = h.Write(domain) + _, _ = h.Write([]byte{domainLen}) + b1 := h.Sum(nil) + + // b_i = H(strxor(b_0, b_(i - 1)) || I2OSP(i, 1) || DST_prime) + ell := (outLen + h.Size() - 1) / h.Size() + bi := b1 + out := make([]byte, outLen) + + for i := 1; i < ell; i++ { + h.Reset() + // b_i = H(strxor(b_0, b_(i - 1)) || I2OSP(i, 1) || DST_prime) + tmp := make([]byte, h.Size()) + + for j := 0; j < h.Size(); j++ { + tmp[j] = b0[j] ^ bi[j] + } + + _, _ = h.Write(tmp) + _, _ = h.Write([]byte{1 + uint8(i)}) + _, _ = h.Write(domain) + _, _ = h.Write([]byte{domainLen}) + + // b_1 || ... || b_(ell - 1) + copy(out[(i-1)*h.Size():i*h.Size()], bi[:]) + bi = h.Sum(nil) + } + + // b_ell + copy(out[(ell-1)*h.Size():], bi[:]) + + return out[:outLen], nil +} + +func mulmod(x, y, N *big.Int) *big.Int { + xx := new(big.Int).Mul(x, y) + + return xx.Mod(xx, N) +} + +func addmod(x, y, N *big.Int) *big.Int { + xx := new(big.Int).Add(x, y) + + return xx.Mod(xx, N) +} + +func inversemod(x, N *big.Int) *big.Int { + return new(big.Int).ModInverse(x, N) +} + +/** + * @notice returns square root of a uint256 value + * @param xx the value to take the square root of + * @return x the uint256 value of the root + * @return hasRoot a bool indicating if there is a square root + */ +func sqrt(xx *big.Int) (x *big.Int, hasRoot bool) { + x = new(big.Int).ModSqrt(xx, pPrime) + hasRoot = x != nil && mulmod(x, x, pPrime).Cmp(xx) == 0 + + return +} + +// // sqrt(-3) +// +// // prettier-ignore +// uint256 private constant Z0 = 0x0000000000000000b3c4d79d41a91759a9e4c7e359b6b89eaec68e62effffffd; +// // (sqrt(-3) - 1) / 2 +// // prettier-ignore +// uint256 private constant Z1 = 0x000000000000000059e26bcea0d48bacd4f263f1acdb5c4f5763473177fffffe; +func mapToPoint(x *big.Int) (*big.Int, *big.Int, error) { + _, decision := sqrt(x) + + // N := P + // uint256 a0 = mulmod(x, x, N); + a0 := mulmod(x, x, pPrime) + // a0 = addmod(a0, 4, N); + a0 = addmod(a0, big.NewInt(4), pPrime) + // uint256 a1 = mulmod(x, Z0, N); + a1 := mulmod(x, z0, pPrime) + // uint256 a2 = mulmod(a1, a0, N); + a2 := mulmod(a1, a0, pPrime) + // a2 = inverse(a2); + a2 = inversemod(a2, pPrime) + // a1 = mulmod(a1, a1, N); + a1 = mulmod(a1, a1, pPrime) + // a1 = mulmod(a1, a2, N); + a1 = mulmod(a1, a2, pPrime) + + // // x1 + // a1 = mulmod(x, a1, N); + a1 = mulmod(x, a1, pPrime) + // x = addmod(Z1, N - a1, N); + x = addmod(z1, new(big.Int).Sub(pPrime, a1), pPrime) + // // check curve + // a1 = mulmod(x, x, N); + a1 = mulmod(x, x, pPrime) + // a1 = mulmod(a1, x, N); + a1 = mulmod(a1, x, pPrime) + // a1 = addmod(a1, 3, N); + a1 = addmod(a1, big.NewInt(3), pPrime) + // bool found; + // (a1, found) = sqrt(a1); + var found bool + // if (found) { + // if (!decision) { + // a1 = N - a1; + // } + // return [x, a1]; + // } + if a1, found = sqrt(a1); found { + if !decision { + a1 = new(big.Int).Sub(pPrime, a1) + } + + return x, a1, nil + } + + // // x2 + // x = N - addmod(x, 1, N); + x = new(big.Int).Sub(pPrime, addmod(x, big.NewInt(1), pPrime)) + // // check curve + // a1 = mulmod(x, x, N); + a1 = mulmod(x, x, pPrime) + // a1 = mulmod(a1, x, N); + a1 = mulmod(a1, x, pPrime) + // a1 = addmod(a1, 3, N); + a1 = addmod(a1, big.NewInt(3), pPrime) + // (a1, found) = sqrt(a1); + // if (found) { + // if (!decision) { + // a1 = N - a1; + // } + // return [x, a1]; + // } + if a1, found = sqrt(a1); found { + if !decision { + a1 = new(big.Int).Sub(pPrime, a1) + } + + return x, a1, nil + } + + // // x3 + // x = mulmod(a0, a0, N); + x = mulmod(a0, a0, pPrime) + // x = mulmod(x, x, N); + x = mulmod(x, x, pPrime) + // x = mulmod(x, a2, N); + x = mulmod(x, a2, pPrime) + // x = mulmod(x, a2, N); + x = mulmod(x, a2, pPrime) + // x = addmod(x, 1, N); + x = addmod(x, big.NewInt(1), pPrime) + + // // must be on curve + // a1 = mulmod(x, x, N); + a1 = mulmod(x, x, pPrime) + + // a1 = mulmod(a1, x, N); + a1 = mulmod(a1, x, pPrime) + + // a1 = addmod(a1, 3, N); + a1 = addmod(a1, big.NewInt(3), pPrime) + + // (a1, found) = sqrt(a1); + if a1, found = sqrt(a1); !found { + return nil, nil, errors.New("bad ft mapping implementation") + } + + // if (!decision) { + // a1 = N - a1; + // } + // return [x, a1]; + if !decision { + a1 = new(big.Int).Sub(pPrime, a1) + } + + return x, a1, nil +} diff --git a/consensus/polybft/signer/hash2point_test.go b/consensus/polybft/signer/hash2point_test.go new file mode 100644 index 0000000000..41fd5c9782 --- /dev/null +++ b/consensus/polybft/signer/hash2point_test.go @@ -0,0 +1,52 @@ +package bls + +import ( + "bytes" + "embed" + "encoding/hex" + "encoding/json" + "math/big" + "testing" + + "github.com/0xPolygon/polygon-edge/helper/common" + "github.com/stretchr/testify/require" +) + +//go:embed testcases/* +var testcases embed.FS + +func TestHashToPoint(t *testing.T) { + var hashToPointCases []struct { + Msg string + Domain string + X string + Y string + } + + data, err := testcases.ReadFile("testcases/hashToPoint.json") + require.NoError(t, err) + require.NoError(t, json.Unmarshal(data, &hashToPointCases)) + + for _, c := range hashToPointCases { + msg, _ := hex.DecodeString(c.Msg[2:]) + domain, _ := hex.DecodeString(c.Domain[2:]) + + x, _ := new(big.Int).SetString(c.X, 10) + y, _ := new(big.Int).SetString(c.Y, 10) + + g1, err := hashToPoint(msg, domain) + require.NoError(t, err) + + buf := g1.Marshal() + + xBuf := common.PadLeftOrTrim(x.Bytes(), 32) + if !bytes.Equal(buf[:32], xBuf) { + t.Fatal("point x not correct") + } + + yBuf := common.PadLeftOrTrim(y.Bytes(), 32) + if !bytes.Equal(buf[32:], yBuf) { + t.Fatal("point y is not correct") + } + } +} diff --git a/consensus/polybft/signer/private.go b/consensus/polybft/signer/private.go new file mode 100644 index 0000000000..c3505aaef1 --- /dev/null +++ b/consensus/polybft/signer/private.go @@ -0,0 +1,47 @@ +package bls + +import ( + "math/big" + + bn256 "github.com/umbracle/go-eth-bn256" +) + +// PrivateKey holds private key for bls implementation +type PrivateKey struct { + s *big.Int +} + +// PublicKey returns the public key from the PrivateKey +func (p *PrivateKey) PublicKey() *PublicKey { + g2 := new(bn256.G2).ScalarMult(g2Point, p.s) + + return &PublicKey{g2: g2} +} + +// Marshal marshals private key to byte slice +func (p *PrivateKey) Marshal() ([]byte, error) { + return p.s.MarshalText() +} + +// Sign generates a simple BLS signature of the given message +func (p *PrivateKey) Sign(message []byte) (*Signature, error) { + point, err := hashToPoint(message, domain) + if err != nil { + return nil, err + } + + g1 := new(bn256.G1).ScalarMult(point, p.s) + + return &Signature{g1: g1}, nil +} + +// UnmarshalPrivateKey unmarshals the private key from the given byte slice +func UnmarshalPrivateKey(data []byte) (*PrivateKey, error) { + s := new(big.Int) + + if err := s.UnmarshalText(data); err != nil { + return nil, err + } + + return &PrivateKey{s: s}, nil +} diff --git a/consensus/polybft/signer/private_test.go b/consensus/polybft/signer/private_test.go new file mode 100644 index 0000000000..15669d6867 --- /dev/null +++ b/consensus/polybft/signer/private_test.go @@ -0,0 +1,24 @@ +package bls + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPrivate_Marshal(t *testing.T) { + t.Parallel() + + blsKey, err := GenerateBlsKey() // structure which holds private/public key pair + require.NoError(t, err) + + // marshal public key + privateKeyMarshalled, err := blsKey.Marshal() + require.NoError(t, err) + // recover private and public key + blsKeyUnmarshalled, err := UnmarshalPrivateKey(privateKeyMarshalled) + require.NoError(t, err) + + assert.Equal(t, blsKey, blsKeyUnmarshalled) +} diff --git a/consensus/polybft/signer/public.go b/consensus/polybft/signer/public.go new file mode 100644 index 0000000000..9004b8c798 --- /dev/null +++ b/consensus/polybft/signer/public.go @@ -0,0 +1,110 @@ +package bls + +import ( + "encoding/base64" + "fmt" + "math/big" + + "github.com/0xPolygon/polygon-edge/helper/common" + bn256 "github.com/umbracle/go-eth-bn256" +) + +// PublicKey represents bls public key +type PublicKey struct { + g2 *bn256.G2 +} + +// Marshal marshal the key to bytes. +func (p *PublicKey) Marshal() []byte { + return p.g2.Marshal() +} + +// MarshalText implements the json.Marshaler interface. +func (p *PublicKey) MarshalText() ([]byte, error) { + dst := base64.StdEncoding.EncodeToString(p.Marshal()) + + return []byte(dst), nil +} + +// UnmarshalText implements encoding.TextMarshaler interface +func (p *PublicKey) UnmarshalText(buf []byte) error { + res, err := base64.StdEncoding.DecodeString(string(buf)) + if err != nil { + return err + } + + pub, err := UnmarshalPublicKey(res) + if err != nil { + return err + } + + p.g2 = pub.g2 + + return nil +} + +// ToBigInt converts public key to 4 big ints +func (p *PublicKey) ToBigInt() [4]*big.Int { + blsKey := p.Marshal() + + return [4]*big.Int{ + new(big.Int).SetBytes(blsKey[32:64]), + new(big.Int).SetBytes(blsKey[0:32]), + new(big.Int).SetBytes(blsKey[96:128]), + new(big.Int).SetBytes(blsKey[64:96]), + } +} + +// UnmarshalPublicKey unmarshals bytes to public key +func UnmarshalPublicKey(data []byte) (*PublicKey, error) { + g2 := new(bn256.G2) + + if _, err := g2.Unmarshal(data); err != nil { + return nil, err + } + + // check if it is the point at infinity + if g2.IsInfinity() { + return nil, errInfinityPoint + } + + // check if not part of the subgroup + if !g2.InCorrectSubgroup() { + return nil, fmt.Errorf("incorrect subgroup") + } + + return &PublicKey{g2: g2}, nil +} + +// UnmarshalPublicKeyFromBigInt unmarshals public key from 4 big ints +// Order of coordinates is [A.Y, A.X, B.Y, B.X] +func UnmarshalPublicKeyFromBigInt(b [4]*big.Int) (*PublicKey, error) { + const size = 32 + + var pubKeyBuf []byte + + pt1 := common.PadLeftOrTrim(b[1].Bytes(), size) + pt2 := common.PadLeftOrTrim(b[0].Bytes(), size) + pt3 := common.PadLeftOrTrim(b[3].Bytes(), size) + pt4 := common.PadLeftOrTrim(b[2].Bytes(), size) + + pubKeyBuf = append(pubKeyBuf, pt1...) + pubKeyBuf = append(pubKeyBuf, pt2...) + pubKeyBuf = append(pubKeyBuf, pt3...) + pubKeyBuf = append(pubKeyBuf, pt4...) + + return UnmarshalPublicKey(pubKeyBuf) +} + +type PublicKeys []*PublicKey + +// Aggregate aggregates all public keys into one +func (pks PublicKeys) Aggregate() *PublicKey { + newp := new(bn256.G2) + + for _, x := range pks { + newp.Add(newp, x.g2) + } + + return &PublicKey{g2: newp} +} diff --git a/consensus/polybft/signer/public_test.go b/consensus/polybft/signer/public_test.go new file mode 100644 index 0000000000..dea0e0b35b --- /dev/null +++ b/consensus/polybft/signer/public_test.go @@ -0,0 +1,60 @@ +package bls + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPublic_Marshal(t *testing.T) { + t.Parallel() + + blsKey, err := GenerateBlsKey() // structure which holds private/public key pair + require.NoError(t, err) + + pubKey := blsKey.PublicKey() // structure which holds public key + + // marshal public key + publicKeyMarshalled := pubKey.Marshal() + + // unmarshal public key + publicKeyUnmarshalled, err := UnmarshalPublicKey(publicKeyMarshalled) + require.NoError(t, err) + + assert.Equal(t, pubKey, publicKeyUnmarshalled) + assert.Equal(t, publicKeyMarshalled, publicKeyUnmarshalled.Marshal()) +} + +func TestPublic_UnmarshalPublicKeyFromBigInt(t *testing.T) { + t.Parallel() + + key, _ := GenerateBlsKey() + pub := key.PublicKey() + + pub2, err := UnmarshalPublicKeyFromBigInt(pub.ToBigInt()) + require.NoError(t, err) + + require.Equal(t, pub, pub2) +} + +func TestPublic_MarshalUnmarshalText(t *testing.T) { + t.Parallel() + + key, err := GenerateBlsKey() + require.NoError(t, err) + + pubKey := key.PublicKey() + marshaledPubKey, err := pubKey.MarshalText() + require.NoError(t, err) + + newPubKey := new(PublicKey) + + require.NoError(t, newPubKey.UnmarshalText(marshaledPubKey)) + require.Equal(t, pubKey, newPubKey) +} + +func TestPublicKey_UnmarshalInfinityPoint(t *testing.T) { + _, err := UnmarshalPublicKey(make([]byte, 128)) + require.Error(t, err, errInfinityPoint) +} diff --git a/consensus/polybft/signer/signature.go b/consensus/polybft/signer/signature.go new file mode 100644 index 0000000000..6bfa665a4e --- /dev/null +++ b/consensus/polybft/signer/signature.go @@ -0,0 +1,85 @@ +package bls + +import ( + "errors" + "fmt" + "math/big" + + bn256 "github.com/umbracle/go-eth-bn256" +) + +// Signature represents bls signature which is point on the curve +type Signature struct { + g1 *bn256.G1 +} + +// Verify checks the BLS signature of the message against the public key of its signer +func (s *Signature) Verify(pub *PublicKey, message []byte) bool { + point, err := hashToPoint(message, domain) + if err != nil { + return false + } + + return bn256.PairingCheck([]*bn256.G1{s.g1, point}, []*bn256.G2{negG2Point, pub.g2}) +} + +// VerifyAggregated checks the BLS signature of the message against the aggregated public keys of its signers +func (s *Signature) VerifyAggregated(publicKeys []*PublicKey, msg []byte) bool { + return s.Verify(PublicKeys(publicKeys).Aggregate(), msg) +} + +// Marshal the signature to bytes. +func (s *Signature) Marshal() ([]byte, error) { + return s.g1.Marshal(), nil +} + +// ToBigInt marshalls signature (which is point) to 2 big ints - one for each coordinate +func (s Signature) ToBigInt() ([2]*big.Int, error) { + sig, err := s.Marshal() + if err != nil { + return [2]*big.Int{}, err + } + + return [2]*big.Int{ + new(big.Int).SetBytes(sig[0:32]), + new(big.Int).SetBytes(sig[32:64]), + }, nil +} + +// UnmarshalSignature reads the signature from the given byte array +func UnmarshalSignature(raw []byte) (*Signature, error) { + if len(raw) == 0 { + return nil, errors.New("cannot unmarshal signature from empty slice") + } + + g1 := new(bn256.G1) + if _, err := g1.Unmarshal(raw); err != nil { + return nil, err + } + + // check if it is the point at infinity + if g1.IsInfinity() { + return nil, errInfinityPoint + } + + // check if not part of the subgroup + if !g1.InCorrectSubgroup() { + return nil, fmt.Errorf("incorrect subgroup") + } + + return &Signature{g1: g1}, nil +} + +// Signatures is a slice of signatures +type Signatures []*Signature + +// Aggregate aggregates all signatures into one +func (sigs Signatures) Aggregate() *Signature { + g1 := new(bn256.G1) + + for _, sig := range sigs { + g1.Add(g1, sig.g1) + } + + return &Signature{g1: g1} +} diff --git a/consensus/polybft/signer/signature_test.go b/consensus/polybft/signer/signature_test.go new file mode 100644 index 0000000000..edf9cb21b2 --- /dev/null +++ b/consensus/polybft/signer/signature_test.go @@ -0,0 +1,147 @@ +package bls + +import ( + "crypto/rand" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + messageSize = 5000 + participantsNumber = 64 +) + +func Test_VerifySignature(t *testing.T) { + t.Parallel() + + validTestMsg, invalidTestMsg := testGenRandomBytes(t, messageSize), testGenRandomBytes(t, messageSize) + + blsKey, _ := GenerateBlsKey() + signature, err := blsKey.Sign(validTestMsg) + require.NoError(t, err) + + assert.True(t, signature.Verify(blsKey.PublicKey(), validTestMsg)) + assert.False(t, signature.Verify(blsKey.PublicKey(), invalidTestMsg)) +} + +func Test_AggregatedSignatureSimple(t *testing.T) { + t.Parallel() + + validTestMsg, invalidTestMsg := testGenRandomBytes(t, messageSize), testGenRandomBytes(t, messageSize) + + bls1, _ := GenerateBlsKey() + bls2, _ := GenerateBlsKey() + bls3, _ := GenerateBlsKey() + + sig1, err := bls1.Sign(validTestMsg) + require.NoError(t, err) + sig2, err := bls2.Sign(validTestMsg) + require.NoError(t, err) + sig3, err := bls3.Sign(validTestMsg) + require.NoError(t, err) + + signatures := Signatures{sig1, sig2, sig3} + publicKeys := PublicKeys{bls1.PublicKey(), bls2.PublicKey(), bls3.PublicKey()} + + verified := signatures.Aggregate().Verify(publicKeys.Aggregate(), validTestMsg) + assert.True(t, verified) + + notVerified := signatures.Aggregate().Verify(publicKeys.Aggregate(), invalidTestMsg) + assert.False(t, notVerified) +} + +func Test_AggregatedSignature(t *testing.T) { + t.Parallel() + + validTestMsg, invalidTestMsg := testGenRandomBytes(t, messageSize), testGenRandomBytes(t, messageSize) + + blsKeys, err := CreateRandomBlsKeys(participantsNumber) + require.NoError(t, err) + + allPubs := make([]*PublicKey, len(blsKeys)) + + for i, key := range blsKeys { + allPubs[i] = key.PublicKey() + } + + var ( + publicKeys PublicKeys + signatures Signatures + ) + + for _, key := range blsKeys { + signature, err := key.Sign(validTestMsg) + require.NoError(t, err) + + signatures = append(signatures, signature) + publicKeys = append(publicKeys, key.PublicKey()) + } + + aggSignature := signatures.Aggregate() + aggPubs := publicKeys.Aggregate() + + assert.True(t, aggSignature.Verify(aggPubs, validTestMsg)) + assert.False(t, aggSignature.Verify(aggPubs, invalidTestMsg)) + assert.True(t, aggSignature.VerifyAggregated([]*PublicKey(publicKeys), validTestMsg)) + assert.False(t, aggSignature.VerifyAggregated([]*PublicKey(publicKeys), invalidTestMsg)) +} + +func TestSignature_BigInt(t *testing.T) { + t.Parallel() + + validTestMsg := testGenRandomBytes(t, messageSize) + + bls1, err := GenerateBlsKey() + require.NoError(t, err) + + sig1, err := bls1.Sign(validTestMsg) + assert.NoError(t, err) + + _, err = sig1.ToBigInt() + require.NoError(t, err) +} + +func TestSignature_Unmarshal(t *testing.T) { + t.Parallel() + + validTestMsg := testGenRandomBytes(t, messageSize) + + bls1, err := GenerateBlsKey() + require.NoError(t, err) + + sig, err := bls1.Sign(validTestMsg) + require.NoError(t, err) + + bytes, err := sig.Marshal() + require.NoError(t, err) + + sig2, err := UnmarshalSignature(bytes) + require.NoError(t, err) + + assert.Equal(t, sig, sig2) + + _, err = UnmarshalSignature([]byte{}) + assert.Error(t, err) + + _, err = UnmarshalSignature(nil) + assert.Error(t, err) +} + +func TestSignature_UnmarshalInfinityPoint(t *testing.T) { + _, err := UnmarshalSignature(make([]byte, 64)) + require.Error(t, err, errInfinityPoint) +} + +// testGenRandomBytes generates byte array with random data +func testGenRandomBytes(t *testing.T, size int) (blk []byte) { + t.Helper() + + blk = make([]byte, size) + + _, err := rand.Reader.Read(blk) + require.NoError(t, err) + + return +} diff --git a/consensus/polybft/signer/testcases/hashToPoint.json b/consensus/polybft/signer/testcases/hashToPoint.json new file mode 100644 index 0000000000..346e24a901 --- /dev/null +++ b/consensus/polybft/signer/testcases/hashToPoint.json @@ -0,0 +1 @@ +[{"msg":"0x","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"8033515766442020753802094421495936405307641096545908290056109643509909046328","y":"11381126110924436284983928050149043794030760993325331452005685965480580232024"},{"msg":"0xa2","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"8839369034724622251878822067778547921981214297487969960744774147002644614170","y":"13307580133018644519978060932060002011221788808981623319663199498706880636911"},{"msg":"0x4e","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13593248839738928441320127478022241325126620785281761779763572214563955950487","y":"9756812130291505793996765874555274713864641348681556179100804083978976657543"},{"msg":"0x1fb0","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"2844374356227464110077075358968034814302320474670417436495323166671223583854","y":"9883407453517367251864368389838694825540457311069909564376892684675876808958"},{"msg":"0x34f9","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"4670702969097965755501877924065903587269588203890798075362248769337163359175","y":"20977575919549418234947718909933326067051424955494271590658005301757631560076"},{"msg":"0x4ce3bd","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13371240673880579869995704949603236804280991198556884588151947068419039461854","y":"9793800896635320519149182450732386703616871602367329634620401587690325976605"},{"msg":"0x3f6286","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"1165733003151401120297673032660093392080351933182997739017681283100124993063","y":"11571418101295194251925500532133979998413049403963667536530147511264830642327"},{"msg":"0xa3809208","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"15170006421353017892261582078944881637812798607259915003324890550008102952983","y":"13849200711103391360855601242094053072466646128455199131974765701057164444457"},{"msg":"0x13472d1b","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"16543643197010375813434528704376251253003807097765409809918423283152588809506","y":"12657067693923333630417455410366864969264140219964014735283871419677627295268"},{"msg":"0xbf5b214a42","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"17674434344482163758572883282122825849556121499278068615096745601294236463871","y":"11245658308194283438416546680963335056632991263403669537202417441186863633509"},{"msg":"0xca223a7e5d","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"21346323725318788768783705728842855070610488203346160628266992984370142987343","y":"19698236716642393719487708250906313354496917276786748077352066996624048632259"},{"msg":"0x29ba15cbf70e","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"18469678768944932856164256854906006798875662120502529889419359668945070530694","y":"19597034417767968889719634426168908421558286196887233284275892932081251876257"},{"msg":"0x6e2354a45be1","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"5667779778052743488707402381745030720301613762813329319179739994846239358495","y":"4799332981798415085343570364027628119461645245626606227653274565862911813253"},{"msg":"0x98ef040b9b3df5","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"9493297785120268425356975306509469659033553914763600938023761442601506214007","y":"616423814144526459019862631792380141896564430651944687951825676552356773518"},{"msg":"0x8660e773439906","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"8019026955588129376869848905677665830562024334737112006280874285232613294046","y":"2919961283188453623448975570025571971724374977143507075164300216655350421399"},{"msg":"0x4cb18700f5db12fb","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"17705655559098579980830388788688740066311881829518775781699722783100112773701","y":"6351586360182732969850425777340365569318760515975442422548820157429435300045"},{"msg":"0x6196f60485bd1313","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"1517713081848641897685735872882349182450339612619011738921488001527979976161","y":"11841175192424863173838268038699294699197585499503428567506383010007362312885"},{"msg":"0xb9c987c675a4938b0a","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"7064918049078088194591647260887767045344736452800668549979511922469145235185","y":"12329519170771601555676297611511763828720101621605233918047592392053339352904"},{"msg":"0x569e8c1619ee900245","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13939404716480828673310291689088687307408064463146342896343781251851210579625","y":"4291977814456751188554419866202195170138636576259611387781984602931259787074"},{"msg":"0xc5b0a2044153942de159","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"16219842437106567513305083337225754768698448646994258500870563042715493651194","y":"8719362108832080112645798314058639082223408053432118639520414936165446790707"},{"msg":"0x77c6c1f532b7d9ea41cd","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"10393793579004007383956919357314390118240866041386621198826112219156240966657","y":"6209254722659044547539001896267745983836238596094443740580148500612357675802"},{"msg":"0x1c29198412d5dfb762754d","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"10339451909542133031572736227695161141879207530821967432927601826684280590985","y":"10284615583570092373661955671655716148444318948712299071260751655994463210812"},{"msg":"0xf873ae893e3c2f1da3b192","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"6310395008770528038998188655125918597012025352254913328039170473598431830165","y":"13701985622047618159942611387414725669315169631732544189136566135129547973176"},{"msg":"0xc92d9a593403c9b32c503481","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13963172467738775858190652987162783541959315878234658374309906371853423197520","y":"11629949515000360115964714874907815205845288084416685395765487750148560074206"},{"msg":"0x49d2aa5873585a3efdddba1a","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"17298751687421959480853061864462247281302217564975955226950992767705374799535","y":"10786743999767499504890100806687367141317862628980984525309893822976461318595"},{"msg":"0x76f1ebc026a51bb48ddce4e8a4","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"1360052685566467023626129332406134523860534912009327223759174437969415005016","y":"21056727349832679303564952387770546028303009151037542358528642466637841602094"},{"msg":"0x66262b12efd08c45e77772744a","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"14726724251774731298940446467087616354063907533952666371958126061328944604831","y":"19333133183779149545318607672528685184735428698710891486325562018297967541050"},{"msg":"0xb0ab6fd9a67938185ad65f7320e8","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"16500409122228125674591277336210563590610850274876245345581919864010995519528","y":"12063736287136235449656559704438380452352142151140460380569130939377349896600"},{"msg":"0x2471749488df19a480a70cee2a39","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"10055778789588173061485669690280343244693521597018607479434971171401778631544","y":"16264500192470638570470904505118328476163483505924722566179015274096468006920"},{"msg":"0xc2fbd0ad964fc0aad85052e5f98055","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"14660165020041644087326725744282567758662792346426568459716972003133361830553","y":"8019121994741568827352153283676799928426893436647389704761234734699312701688"},{"msg":"0x7e441700dc1fdba1d8f119e7eeae0e","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"12591620610445237092062067015202314134200141336077275054067055324552455424116","y":"3834370385043011118536434622702744251889302320424552808182743528725266809832"},{"msg":"0x3216b3c36d9cee4e655478cbee0c784f","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"21702626586312740560356934654895332754497051959864769889321381856866714143831","y":"7068588958345664598853622303667733312689028292984428492644504938654293191102"},{"msg":"0x9b983530efc95b435cc1b246b1984c46","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"21711521635867857606492584166111315535265023765301755424817002276202663501287","y":"11026433926539627423486444646484012255641169172006596626284279481350630915418"},{"msg":"0xf11b905f697201029c59eaa36e04dbd037","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"14931745054564226170547991099552812399248461207586983210182940630511087808910","y":"9733581514276268123814016937397680355519338138619145323803998337117366579238"},{"msg":"0x36fddf1653e6f0c96d033931f45439a021","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"15351008194546871440778949270548096340532771494797526736048226493333679249776","y":"5174400911319711371697632697732954672421722470005414150600208229011505643250"},{"msg":"0x68d640ef1f8fe91950db396644e83e2ef8d3","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"15831099456863359371350581571989770589643727098005647917604911294775097206220","y":"16440866498879893889405982573634642071544131624796865275494417612823650990982"},{"msg":"0x42cc4bc36b244bc5cc8e4408e3f8d866481b","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"688371554316569613984222308150517901653682843282726662190769206638996080409","y":"13876625913877608831897900041188961843335666985547881323690448516101373744173"},{"msg":"0xbea7a43cc7841cc87969df0bcb0e894d57f33d","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"14344541141501194742260389902817945295475381671276440953567742892943154683935","y":"3271779929868696433210913349277544183404492654377366184982362184478772911799"},{"msg":"0xe7631b09b1e6c3919519ad29f2449281234a2d","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"19231632652843681849735423645793809499528074131663285750629409102171536673742","y":"1385830104744656733345241610414801982846743718625276085790037175539856861214"},{"msg":"0x30b67f0d8fb9cdc3f60695ed9763a94ba8b7ee5b","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"18787639737229759871280495419150182253817859369430698085233876389021338930080","y":"12744984302396877791412074590911542301335877326493735175499225453163668172940"},{"msg":"0xcb0d967091ec9796d8bf7bfd7a2933c9c70d5f20","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"3360456086856000090865015924123485380739124369767610568493212040732403330074","y":"20063689834772763101363541613060462965213616635229806907424552040391067337667"},{"msg":"0x4985d090fb216df842fd79dbb4d32708b95211421f","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"8102545676126634302113506329913578863431608541812667763920250559133620234203","y":"9444783700534459933188528045913910854323267582143322950291971350535906828943"},{"msg":"0xcc702eb0d678cad78a1818764f712e468165b3dadb","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"14315093171172966264512516018638167070913356283047764153395156524133694763741","y":"3449733968083082972777153518862315928315943811235849532384905661766437645590"},{"msg":"0xd0b58b81295095f386f37a4ca24ea413cf8d1f782158","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13159149118962626230103141223101588242367572065531983340287332394655807425162","y":"21539181235894785629600298743678121654014710959294407000485671425732730689240"},{"msg":"0x7d8103c0c5eec5e8cd7d0cff4f43b480020578ca8c14","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"10761270007619243731116959632332398180696628720267145249206289032046500492621","y":"2927104350373602986338871668321237878955187983361925590635451907725373509702"},{"msg":"0xa30a7ce5d80cd2580843617ea01dcbc9b6f7a133b3ef9a","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"11720179939988362605639657093107203573268380423627817249929512533680334749642","y":"863701858875881504397956184997723623343880038940542015374090296395421092266"},{"msg":"0x4ad9c03d338fe0f6be0665fc7fb6eab6ab3079b1761edc","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13335806860201365560619826113585206708705864249641961625589650854128194037247","y":"1452718324488850488946882245073930842156447342150144665807982918199327214336"},{"msg":"0x2e8beed6fb5f9eb87d855adb49225b80f0f5858aac6c9720","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"15918141842596659486724435242935555310653502260185959644464522935809240877018","y":"9950334061241012993302704292501505948854403101649992651338790870196392370361"},{"msg":"0xd8f639f86666862cc09d46a2bc68490ca0b04c586a35f16d","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"12408232226714044421863360719325669540107948671743275953009852268416735503280","y":"15236221548358620707234803475236390086844055656799469313725838882760399041840"},{"msg":"0xd10806e9e4304d0598d5edbeab5bfabf672b8e08f9b51e8069","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"2027322213479691161647620661378228450932237682435627777087257758966479901360","y":"20620643409579684702872397664289973113670001004556392742174762168355610818904"},{"msg":"0x2916bcd1966abac607bcd880871a31d6637fbc1bcaf3cc69fa","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"6809263582105503633872132725140710367849482510505262728982712450445603344803","y":"14562713620602211667738558970316861191515313813725646845811859339113511192622"},{"msg":"0x7200e15d2a556057a9a6384cb263f02fdb76e40dd624ae2073a1","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"1502123690869627900667257177566865066562133929190326763187443648732152101476","y":"5549229958741496834114589911036133672183751817609100637096614575124552782910"},{"msg":"0x28f13927d72e6dd6d5f61e80d4884fd1c603edb2d9a0e4a23b8d","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"10304639158172207476744937981279420613714815899998248404640749455109315828367","y":"21747987739310818818631864245665352510358760988083111590430646778170397284571"},{"msg":"0xc7722cebd850e3b14df176a9433d31869f83155eaae2eb55b1fe78","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"17742289540013609027159045714821903988129033205226660834675594487688109979973","y":"5735622002978076385714068777456451911955912602530142039355049604930305019974"},{"msg":"0x671d274ea9b32d25a39616169fea8c350927dc2318f66ca5f2f039","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"8074249347800580742748871975421282832926415311247486427786365835443833025090","y":"4891317242266853697108976451255881823356085027900984051155113908021898544288"},{"msg":"0x6991a92b09461138c1d31c6e1c5439401eb02fef2c08bff73c946e38","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"9777647674552733070398256613473406188955792027829331231119539782439263802724","y":"12936232326766621664722047582576793970366189327214069209495856808442166503292"},{"msg":"0xa5118bdd85a59b5d1e58e57d9e604a12d2ef12b31d943c3ffc94159b","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"6911710008037549605399138348615956724931732789817917640786885910641536664897","y":"5710255230236402339836457414577601356021584914659710981311644871588354794303"},{"msg":"0xcd84ba3e0dbe9cbbba8c93b13eb7360cb7e62e0a83e143e9db5cf48866","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"114928414137588584192062375432392884073966450431612893675821302655265351911","y":"10024488345569601514439090205982824385406493459286627367279396715632061024452"},{"msg":"0x3c37527a4685c19267d8ed14d7c0c476f193341a36e4cda0d87124aad4","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"11075228983627543294786827919196414378824978487508877488397846541187452552673","y":"13287537958652141344803802215839141530238960372863900077124507985077865135130"},{"msg":"0x23dc1767e85f613bd4ceba1aa71f45a6fe3888608ffbb72214ec508c35f5","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"15647808310239713843583080466238698411031040903746761457865261259267258656026","y":"19578430150216359845830344207253770501322393842662873957778687291821325705475"},{"msg":"0x3f76356ed94e3316bd641378effc189fb47ddaa443f67afb2a0a6b1edaef","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"20543564939601717252086710588407933868857583811120552434904360462539284755289","y":"6939231199908418850142175621341218894990933346369935839093195675387201703806"},{"msg":"0xa73515fea70223058f7062ab572e82042687b2d5054534c27339488e5580ed","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"1083782306399320847217276218212286108191149637836729279832577271145323924895","y":"6156137253782052981952341416865958437796459921075430673801730230679313560658"},{"msg":"0xd99bd91a1e5c1cf01481ea85a34bdbfb90dd3ba2d4aedddefba5d3e6521783","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"5534736904258239847512537722738366906816958818370405397416597144289058389775","y":"20477126482362052776768987555174543681748434836453785724505168811957222127059"},{"msg":"0x7257c8113373438a2253d351171032ee3913ca1b2dd5c16bc35f6a9306dd6cb3","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"5231848618322669037679758587282132468221282873293999332752837272923209687855","y":"4607458091462532255228477277325992249066148179343890750719642483084938449729"},{"msg":"0x5b8fa77f1e0bef1cbd920da0f25b9e83ad280f120f5794aa6fb81d7a19cebe7a","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"6926249606174262444216325473393581624359621344978676634271558586295227215595","y":"17085223196156087250112556121994759345702690889981779941075029769113692941681"},{"msg":"0xc98d37a145594a73106a3619d6d7012097ff138f2f8c9aac1ec14bc2d3ecdd1c27","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"9890389593820569080996294491057153079238579695068920346304410759003494116513","y":"16562179345220161450789150397308063485404302320094382428171869171050572015008"},{"msg":"0xeb195e4c2b99ab0a62e5040821103edbd5235296cb0d94dd2787d20784b452a4a2","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13737765326510490705882651679546632988943875979146214775711283042022239559078","y":"2189364963024970632396976945482187089184844615151345882474862345377517009814"},{"msg":"0xf74ee194434f28cef3e8effd001019abf9a4821899d5f1dd61079fa599ca2b622736","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"831482273185731911485692763274422794126676256689905985353301184132771951145","y":"20053793446882883628484311025485553546608905505742487531008972611250704301222"},{"msg":"0x55da5e73e0d878ec9154e8b6801ddec0214a0ade9192510e930f3b5808088bb1a075","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"12644334338447027950001097033297744272633651545020097966702721186146922268833","y":"13193114279204941220111181267645947192884644561823389546448275495970796351949"},{"msg":"0x9e1b345e6f7682963e2e0ae99b8db7ad72fcc363de7c14d0b08cfefae19b0575d6fee8","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"15370321941963242269015567168121491822631355492758463937986009493585975049054","y":"3732306007376528133014757337422903191583476219923540697620958345838428311589"},{"msg":"0x404126252c648805e9266e2d0e8f238b636ca079cb1c9952844abc27f175552b75946f","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"7089042471744206516520385553234964571953164703884702300346021574682397384906","y":"3588197228935221603861107041122720553534259583255020636703491533108533628908"},{"msg":"0x048502e4d6cfb651ebbf4013d0d8b02d82114b27ee36df390e6e0fffea411c90176451fd","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"18992989932144992212862848662087454723234877471042830444196190092161227664807","y":"7870904037897693645177825687720264277512402641595875974707378383587764072438"},{"msg":"0x12562406e9b88e3941d907eb510d59a330c7309653c34f996bc63c74a0f3a94d59270df5","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"16413698267694232386102499661531008942547577500274492562073493750621673516955","y":"2341977315380606435729008426452418481415916294934101695922986697578525380688"},{"msg":"0x18f9530df142f23d18c96e6966ecec4029a836a0d5cfc7e96522702c44d163c2d767e0b237","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"1576691211794802908319506685152525397395229457709809863847004469815431003218","y":"3633234770875806345882293803423584805316367766201703705369314412818467835574"},{"msg":"0xefe5e46f43545e9ed492a3c52e9ac98ebddbdbca0040ae67ef5949bd5de9c8279a909a0abb","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"14066336736811753160704157100368584180625863279158924762147066430769308264161","y":"8116666466939888976799093494597008292568696773881043932445216232892536039004"},{"msg":"0x3879bea4c12a2f56748689e0a23a0c8fec50d679608942c269ca69c46f0c6bd3acdd74d40942","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"19047524785943944733125153879926172627549027007446621115355014039876669552298","y":"18586356043756044543417689435671820210395179759820073774709700050277924982824"},{"msg":"0xcb74d0d411c401ed441d1c7b5341ce5bc44e33ca9f267ca6b1876064a45b5856ac975210f535","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"5170649728473215259256500046529274565347223880631468489950178729861277138640","y":"2821723810265874307629429271131115588089652348646519504911391137193195942058"},{"msg":"0xf67a8336604012eb71439b3fb4bbf201fc7eecdaa1565f06480e2e4b6363601e29e0dadf2b803d","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"875665512814912818940611259939883802473482353288556020988536201351057737123","y":"17889088698395544846017742950558460404709756027358032451725156144388870791983"},{"msg":"0x027cf295a29065509449e65e7ba3eb964d526a826f980242833f14962c723b4909c121849e37c4","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"5136344722840071439324037327124029678469606276343174537639470803090547391588","y":"19129995526768662076138816042156841087051093534952278249147891186231208186966"},{"msg":"0x325b1114525fbb48fffec2f6a939809a5a5c16c9ac68a109e98e29a8c039df624083d73200ecc431","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"10255765689747844697649519571310260291029754652492947637568139095482100353136","y":"9728062678201799761722832275089262551417620239284118034245619114988464623173"},{"msg":"0x8e1a19c78127d3edd947d6e5362843312c04dddbdd7c7a0a05c7101e00ad236f385dc76f19395f6a","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"4006394149441852115878543035523287908819016379032836246707468538809870138695","y":"13664610022603849491340050181372433184232968476035734348280784274712178538100"},{"msg":"0x4fefe2d48346cabc39e5f5db403f4873a58a524f9cb9e26f9df2858d0618977c9ecba315f6de6b3d69","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"18236505907974871409014965133925692637948015458518855115277826552072604596952","y":"19619319807355788013989576792095544493285950251846467582821755311239081015260"},{"msg":"0x4eb482f910351a4f893cf21c3373f983ee621b1b00e8016fe7401868fbd59c40a2f3f0b534cf0fb7e2","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"2694077699648839852905046751960345059479443757919356756379442059901522215824","y":"4905944289645424045299788076385603841840536861236889510258998345600088558962"},{"msg":"0x86b4acef9b070b245aeb7fcb812b32281879fd38fc420cad0481b50e1be424c53014068af13818fb7b76","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"16099319090983455113636395392365340221790382802596620502938460028280881671370","y":"4294381232991437212194811993823648409960410743745678458689118173700552356374"},{"msg":"0xb1dafa550ded29632c617d6cc20f208d38028d21c548ef948e2bf2c003c7828c00bd99641fcc44a36706","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"1080296238333079093799798385527615556146022795528422993936061031310883286788","y":"18950777435154330022929367215280459856520993550752231671265097223981370327675"},{"msg":"0xff081b60ae40dbd0a775ef1e1e969348a1e69fe834dd09aeedaf21799106047d25e0f873c63a3a185e6d2f","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"16250995193224198595894728647924954799866833505183453075844941488927433260865","y":"1295745618001305083931760135958913527022404191726394407031635556242310126023"},{"msg":"0x34df1fa183e2b751e599ea375a06140c5ed538db607e7446127390797f03f66f5578630cd378ee085a3a30","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"2074680971404755602466619498193201710364568065676097354706864669295254541448","y":"11960107684270344313256625757108945895804183167280600674949982749383656155606"},{"msg":"0x4e099f2cdad15572e573403f14b1e8f668646d37de4e07740165027fafcd6eb423fd0964a919e773e820b558","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"6145243313092055960268160662953960735443762940478175392647897350134954324503","y":"12863923979210964955816265019930929521284194755563486283137724033230533671089"},{"msg":"0xe38007ad255fdf7352869e195a6bde0157ec6e98d3d2ba8fbe916f48fde5c3e519fe6ec21f6d065b826e4005","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"5148469828355637137162610056147405830957786256710882442725568469189498776405","y":"8440883175611297801037114559688380653994774380438813366296254114158177633890"},{"msg":"0x1a75fd8b78f199a023ca8a7bfe1db07ab01086cc9986a76d8a000d210eac2a4fa73fd1818f2f940ad16d80fab7","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"15874914790998377983137302903314184819207934934135754862845287741757311290109","y":"15839381995698748042913808947635710844008883714953975363324092483971099838495"},{"msg":"0x13f16502dc7834be6edbc76719f573a71dc9871008e9650e583a3b81b36e2fc75db1fb1bbf98df5993a8c52db4","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"1053849130621952541997444922042271344454831197941605910803690883634119487384","y":"8113398857277700842075936748025667857095276458861632291784147752146680452406"},{"msg":"0xbbf87d71cd1f7625dcb6be855abfd811c2f45b3baec26bbcbd6d9d0bd608fd2503ec66ee9a4aab394458b5b3a1d7","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"5994374253873917735836852625855086495594808570074998954550414045509604559540","y":"20025064981324005436521103835854387712214067678713099567543582859446971716575"},{"msg":"0x7ca15b57dba7f887e78f36a00ba301fae97607efa07171d584d87cc635a7b6879a3ac440cfff7fd532c718804680","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13107819251465057290949628248586258231471978203615701758465509920010599862969","y":"4333317483774257913985462259889135520133655282830524201234033671822825498320"},{"msg":"0x0fec03de7ca58ca834e9fac91523e24c1e83a3a10f91b3ed8043c8a1163138d8150ac9a01fc26c27b3a64fae15158b","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"15576030857155748424210813428426702760719944178926591910868292261304858664600","y":"9690824739131838123506526684868634027267471186481871791442841293138031828431"},{"msg":"0xffdac16563502d64d020f6e0e8a7fe4f6f2cd5a052f0996bddd0c443a2b83af58ca7c05197291c625ba895fb6f36b3","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"14748436175250811344043307625345907098195531040930879488811257051173412979362","y":"19509598808365544409816771045857020879085388048285914605052445200987167697224"},{"msg":"0x173c65246e03e6b3ba43d9bd8b7b21637e6cb7086cfc85a5d0b412ef2b335ad85a841bd13c76b5b3ced697ee17e4cdc9","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"6932493042871076272732880072694975754164901973662109939571196788027753894671","y":"19737656475378861110799767889625431482266904103742273620345645066517773029658"},{"msg":"0x40bfa0dd93ab0c235e9ed77489bf15b62ae29a435a3dc91b3beb3301ef3b645b40b9587a66138daec90c55af5194a5ef","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"21725280010219771656033662322596383159888441241880953051428767516307877124306","y":"629990868005348424526013889322419996374710734469403287570094691281703772095"},{"msg":"0x9b8c2f3c5f08314344b50f45e93795659ed041f1dd2411629cf4652542a9d3565b1069b6186c9c8fe68d6f66e4ac0ee824","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"19579494963030594209365770160519536939940724787486592551952432770614155381689","y":"14096776173716730802602900007437582410758657278457068891744345240759403526670"},{"msg":"0xa1fa0eb0aa39af7123c66443497f34b8c4c2dc51b3d581345b5bde3552af090990363fcc7023bbab80abe7918325da6c1e","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"9555905702798330097759071024931904528558344704490250805861811729395330103316","y":"19056188959310832691951633359954261546739077029988031114937630165628526897882"},{"msg":"0x70d37d32f413c20fea43583709636341226aeba4cb00d43fa234383487ed6d18a1869f810e8ffe2c22247516fe9aac35f520","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13652218558176260611438034031968590159682346418862079532753625322501168737527","y":"20397805030720851183159779739582913557815044044114318581126550281446133235045"},{"msg":"0xf9a3b4664dc7f53ca9fe7f6210e7749b3c2754208d5dd590f4b0097736251f02aad9c7db2d2dcd0a328c848e529af4bae35d","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"19345831185959331773845603385348644215954218680801252677200524370538854874364","y":"6386226330098705709316414291992595868365478412564001081470226989687037385175"},{"msg":"0xf1b5fdfdacd861b9a835cac4c6a489e74fd7861119cfde778c35cfaec4689fe937d19e9998b4b457d77fffc4cf667cdff150b2","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"12791345812894243604612300120709761128456900205665157829994476053791691852890","y":"20352983139807434983694402338201606975840675643166928661179784676344469379010"},{"msg":"0x7201202cb4d4a42155e7d3b54982d2085dbf4f994c2e4e2ccd1d2b49656d7a1afa6a7290bc4899822ffa30aa05090ee766d0ac","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"17386908977122198095231011878053849184695658666040826434557289746849537426546","y":"4553092862931292718197149193727196702796311496446174881310982621740561882254"},{"msg":"0xa7f94de29a830ff54cd887e8637212fb4c2a4258197c965cf99f117923b16876b0c7b6fbd456b36a7ebdeaa942adc17bd6f08be5","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"15968530554197896055075811226718089287531044012759246635672059799407597076121","y":"18775455646209587962056366955243896994937368008174482618253482372071655887689"},{"msg":"0x41038c11157ec3fdf150f74995a6fb488ac2eb703b685468477af17ab4b70dbb85712952b3fcce87ccd8ee6271a1d5226f8da1a7","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"16323270034971821750358370809570128459235713061202583497494634083348419102946","y":"9912337511350549566966442501560590690259156440689199728216559391843040443648"},{"msg":"0x3f9388d854e5d3d025a171258f49e46624fb5ac0acf021912250b76b04bbef0230827163bc5b9fbd6aebcd4d17338b61aa3dcc0c09","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"10388087513303348186499582101657164666435287523230337543048509075200140911197","y":"15134211737699497484705422077182889705265436686583391670951791756380141081903"},{"msg":"0x7de0474df3a3798d39ddda1ca23817f530ada4d4aaeaaded9b6627e9bcdd0871aeff6d7235869257773c02c3545bb2beba11695e44","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"15311535981759327749482077435124331112160261682955899777700727626663941523092","y":"3840945590082397157422303145737002075586985085698765735048931164480883015665"},{"msg":"0xab2e0b0f47e5b5fdfb2b2765d792120dcb8c75b0d6a1833621e28606d4ad7f5bfc4bec4a806ea203ebfa7aa762ee3ec8fd22bafa9bda","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13748310829216625042600698823558559066893317597475694670280505451700853918594","y":"21643888065575449039251931748647647606833764321807830929512174840179275396814"},{"msg":"0xe2cccd966bc40d21b7ec836b20d2dc2d3d2517ded85515c077a92becc21c0e92b81c6716f69a894a5be83aa06e05e554dc6791259cd7","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"144364677640724139689015520651044457367212617158664691630783276805028563863","y":"14639803541960258167973582402990003285403127866723620611812006430168404443925"},{"msg":"0xd17bb30758ce12985951a2b86119786e0f7e018af44fa9a5ec6a40bb46731618ef6c218457dbe29ec55f15fee91707c32c51fe7b4d5e3b","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"17623493321792781724268364531195567571018816613546396479810930055550044267153","y":"11857157551073344547031479812766860686037821724100724145700127206015572983805"},{"msg":"0xf8f091ab82533481e1005bae82f51688b1e51f1c2dbea2cf02a13db9a7603b7f941c9fd71e74a92d765939402ca9c23f60d44c16b89a07","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"15617160102216216667851962786797335110705190517202281568649551422552898751522","y":"14136316679282798265022366071502451647878857290717131988328516136089873279800"},{"msg":"0x71b49d9bf53e1b0405f49760538e78ee32c07f7c2605eca2483bbcfe053f32c3f6e05c245ac6ad433494b026d21de0bd536b928477738ee0","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"473755846945332013502378095443683033399382507360409137453198753708062209377","y":"21545775027361069214282143958887027531455396073200999222887251748523258401571"},{"msg":"0x68969dac0ff8510e12765a7a5873dcf6db193f6c3a5924baa64667e304178d04f2c99281c30b3d7e8cdcc2f5fb9cf4ff8beabdec1a350cac","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"20686115962448499563817298298126624200217214843022027367177894674172817015123","y":"4563488203868430039237101785182068116836217717927889539726928410351275576189"},{"msg":"0x282cff7bd428d926531a0045c8545cd3443a580d34fcb47742ebb21a6076c180c48870986c4d016ceb3a896c48420b3cf75e22d223fb09abfe","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"20269555139965399829718019630938882995098933891531344630483513748924269564085","y":"2228444633516377129397848362147250565957923926518459722147648871307952093224"},{"msg":"0xb4e684ad82491676c6c5b5b069f57cfaba39594cc6cf14b477ac6ed4954b9373535860329f64a86c430419e285aaafc21efee8476404e38cab","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"11524802166479066258832685456298492491409835149357552763291986289450597480468","y":"5960560001772472343298715027788488623515493690008019169691113039565034196077"},{"msg":"0x5d2827d80fa2cd6de41f8ba961699048317da75180af538ac076181accc1798e4256ded1418232a247f86d3f3288bcf60824a1382ca208a556bc","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13449771948505000074269760404813825589646291097529664144142026493808961554569","y":"12848691934131520967866355735073833977319464607227109767072675477647163696799"},{"msg":"0x1c82015320736d31c44a9726b2c9c1c9b4e45ae39650c3706f96f062fbd56797feb42235f44de8d2e0afc47f56e34da29090182db69e969ced40","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"9393414662235403916194314513580384493995312389500449918283389648821549272511","y":"14570849814847246734796545645185698844597390460393758351523431598836549679940"},{"msg":"0xa542f99eba5fdc30e80ae9e8a81b9b7c061c881745bcf95ca2b2663556fd3e5bfe07c9791e90e345783a8039cecf63b7d7e481585c35a65aac3e18","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"14368054018159033258304555481364235554001369600292888530047363192144173454571","y":"5206917728423724165241055083886076357723126439894076948411745656433026184397"},{"msg":"0xfd12f8f9be6ec65bd5f92671c6a16d2790ab212856feebcb808b2213683e2bab8cbb35e1b06810be85cb30719dbb0b89afb61f000af4a9e6280de9","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"1187694398528344971448634601471976617878404109897248458932219511600994222280","y":"985450508898864992343174595035318035643332585408661699217737683728406756492"},{"msg":"0x879a229280e1413890a84b76a90327605b206cfcee5a73426a00aebf9f63c485bc88681c43b36d7c8f6c5a81695b8035a2d04969b6dc9ecec7460b6a","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"21560072522711277006655743747211247164814979442933840370656892461430754297425","y":"7587033593471231145205335134461633628228258107980296039968357772651527691930"},{"msg":"0x4b1bf3639d28419992b81ce06b262be63ed3385eae2742b131a3a0cddc29bce01a2fb6471ae8802119d43c4270a3ac17c2ad3e8396e938670dfa58ea","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"8347451889753326593570155802554341831561137613134123610521498971507031210313","y":"15208951501095128788681916520983492748352251696448629192578142972846084445244"},{"msg":"0x94932d1c48690096ecd62a6812088710dad0e267c421773ba5d3df8cf92c35313df78e62f7fe840163fcbc77f598dcd3bf533a03bdef49b72c927af46c","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"3588962294082434721405553930494194411009629248836599198732994092090074296037","y":"18771469976328837237538763154379321379194204800516836767101021447346174713506"},{"msg":"0x9c405568cfa7f9268a20d05331ebbaecfb8dfe38f3ca1fb9bde438654b076cbd75e85b04a58471e9ccb2cc17297036e56a365ab18de30f1fa49092bfcb","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"5290191754448775600993663173095446324732428159314545277288893355472354284767","y":"6637898393086569429360853581597854879471438632073073406300067655782460383684"},{"msg":"0xe079273eee92003a959aaff12c316ff0439599ae5e066b334bdfb020443b5b342bbeb66ad19bf08dfe92e859b9ebfc931591dfcc71ea382f6dd004bc2d36","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13323213670706424558889595325951815815812109851934095869096919854093610404254","y":"10253858166639021595887953963826271677514533268105279496930093202341670713537"},{"msg":"0xe686ca3dbe26a842c1ec713d7e09fc430daa1c5a97ddb00cb84b6a7994cd82cfa23d33778402b06d0cbd713edc90dd41c81cac0e01c31679d4e7cc42264f","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"11280977165675830891512695184788649397724141443125041951215309932791142128208","y":"5575848558641629717959340416034080419977779383598968826560008465395323330180"},{"msg":"0xbf634e503ad9c2cf97748538984d60d2bb95d36f7a288a8aa2bb77ae5968f6885a397152b4922336a984d70595b7dbfa469792d8dc10b209c70cd99d204827","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"10059027930745534610594979077881568930283585726801817856324249186221922462554","y":"13789729803445723055332071200298511747989907473712124147549562175661131182747"},{"msg":"0xbe5ef9854d90e08d66acc3f9defcf568a0f6c3ef2ea74fe1917b7b67a9fd50801d967fed13d2f49b39a763e46be89781a7e3b79d3c6efe4efbbaeaba034e85","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"19611965913818777764446948088672618177480924790993937647505513671724597905881","y":"1832146265838445673018316710552648711860740839839198113483757129811176253332"},{"msg":"0xfa51a8d1fdee8b78367bbc025ac6e30d0d8d319c7147f13dd4a6503c6f7855fa9937d1a3815af6ae80cd5c0a627023d156b23474f3ae4d8009021c4a116a6959","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"20081473756752540027462270798365200575318343160621663532711037745216186565790","y":"9252175627212593064245436685930013753312859804718342352837796129906719808143"},{"msg":"0x9ba5bf315be6337524ef4e3d1f0865e24e369b4f0d94b91dd5b5d63add86a53caa144d4a6b1f1f4b40a0edb2d346f52fbfae34dc0143e2bde77a74ce6b15e045","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"16007000126753099056923279021991060954874773542288303878984082095558445375931","y":"4034075762647719523139222446502698227528415873530775595078599975771138768568"},{"msg":"0xfb962bb7cbd57ee320e11692fd5c0001365b304157280d9b9fc52b2087e001225cd495252272587cf3e7e62778f1a3e1a98cb452872f314d42436122e3f6241460","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"20635122404702174398907541419238078667145395250293399038367676356187302455792","y":"18092410917668497666752635404292539073343676062034579111745749248660912059938"},{"msg":"0x49c9648703c80b424031b9a619318bb5bb80994d868beb2158ec5a6f285aecfff14eb7d715c32c2382107fc00482f7cc670fcecefcdc46bf06c52fbbe74e008d14","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"18260871117070843027128051420598476259215725161099969164119202101307006037939","y":"14281359929738851779315653223164793909499280231046264538385764350642460247958"},{"msg":"0x74616ce15a420449c73581dbb8c9cf2d78d72a4aab9f9ea69dfcf2ac3aac3f4e95789786613ef9a4542182b7f70c16fae1f0fec65bff0dde03f472a1084f2982e07d","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"15571449999937705540825263223171604624778338369789038765153073964597050246857","y":"11431507112249558281684828255245314847511701535913170032808162970132299784402"},{"msg":"0x0edbe815254cc7b821a05373a8d10d7fb3314172dd017a844071362a6c864aa3c3b26962706a24dbe58d563d2cc1f1076208b2adb5ac06d91b61b80953d7d75e24cd","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"4896410290680600209739778700301117096103921289626580638253367641127804395017","y":"227125274878244481082245990423988539525218308669102425589763676711594137118"},{"msg":"0xb8e64e0ddabe99b39dcfcb6b072d556658612cde55d6a3eff54f9b882f1fb30240c8c691c572fc2988753d15f697f168705aacdf78b3be8279a72dcb493e30d33f5bfd","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"21547518864459829958694688950209767125592755556095939607117564086305328507599","y":"20067892301696840834916260844884669528164546149117126623388040339268401486903"},{"msg":"0xf447f8b04a862c466c2c39238e0a09835c37a11468461c183ede4213cfdffc0038b8245650029abf29fb8a336aa7f9f339a6825de45453377990a27ee2487c4a7151dc","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"686495967560516863954061128898243680499790065891738488709609864837808259115","y":"11384266588678395825939911307157845998207299438890538003651364342333319220645"},{"msg":"0xd44e27152e10bce2947b2c1c5e0f0d84bbc8ccec685e4336b8331ab43f0f486cc16c667751b9e87c1c74b6788345ac50fbcb002fce8204579a0a19110f289603198d6096","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"10919079506390779313618982542495270309538678853002055529055067098063140472059","y":"5913986180565388551620545383406928611605512209953034614176002142173970452329"},{"msg":"0xfed46cd19bcd2d34a18bdd0e5a85185a8f038685e2783e82295a1b2a75df4f3987ef8c30ff093db6060badbc64df19d2cccd7c8fc6d1c2a3736bbc300996e3e87cfdf189","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"5534971101223098887732105780680339930810796475058889810928604921330456843542","y":"14633226811103112971729263303688522000321634964728739730841431855185060141087"},{"msg":"0x32e13142962dbf4d84b457de9e08baecaab698644d75b3d8fc12142d0a8c7b3dc8ffe177329823a43d5ac19845e9f662064de06bdf106e33b758a47742f8a02da0676b4e5d","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"18069414260120002125501855369323218569900691934172112259534181070347454023562","y":"17130795570314039634399413126291195169718641189265290573453731439164962408287"},{"msg":"0xc1040696f8b3371aed83e3015f517a75d82e5652d3867c655e8520bf21f496c2299f61dd7c707ece1155abf32963b92304c20b3cbc82d31f22832f4f68957e633a4ad67bcb","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"17092901487306054188485425207681144298428567317927090180768442579834439539453","y":"6540051675172427927672542087388755531749457062285302932238298748748541114887"},{"msg":"0xa408bb4438ac08953ec02790cf85bb2c65b051014e4d1929ef23fb378d07efd4c9cb3a1286b46bf40bfd6447ce915d28eb25d924e8ab2edc2f27f523eab3c36ba5426fbd3b22","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13908371721672107854541429922928666891569631061313674569600607426055595417553","y":"19418005539889304726876759529342832005507959378788274392980086136685938872564"},{"msg":"0x47a09e86a5f1b25068c856d6a4407fd4109865d87b0d6b03c51d3e36627e1cbc49d8520ff071146478d20606ee504d89d301fc428df34b444cdc45cfbabfa3123b9fe068776f","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"19069337178813385937582048199440365109576842898041646405815823292493324688428","y":"4061912216209926884274929152053991131263488233348814260965357235833148500853"},{"msg":"0x5f6f62669af39468d4fc9f69c2ceb02c52cf182f3349c9ef4655868f32aab4938c7a8e596f5164eb089c2ba081475cfe3ed10efb2382740a3d9af1a620307cf0fd8d3b997a67d9","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13781500879382423914883776829149261520767513161679696244175848029201367228099","y":"18186287088489520388895126341171761341360842458946118206590557850638864827343"},{"msg":"0x7629790b5f12e02d79ed82c983c229efc02ddbc292d36ae345b156d2cb3f381c74283692b862d182161aa2bd94669d5ee68c6b9752993d1c501bd30b290a29d9a535f8e3fa948a","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"953395871206754488920326968302667232621949934985101645455966406847979884161","y":"20548665135251727720500109681556089894030806300816575861707094614861429342157"},{"msg":"0x15d12447b70e2c6ea566cfea37b3231b18986b150ba39c12cf653fc1c79e8a986da41af836329bac50df1568cee57fcce6192d461e8251d534660ac6138edec95b55100bc83c5145","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"15030639816461241739608948897095575325906569905556669819948856239596567996956","y":"20894580394899904700702126366122639629331095743628128706570125249429505140081"},{"msg":"0xa518c8c3cc61cc421ca0117f99ebd671d1479b66cbbc9dd8a6526ff53e4c690b9204508c7f205f03928a2e641e47b4943bbbfc6e4c5809db47ee4286b783c758e5286b282bdb34d6","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"9288746904157860921703973107511379595648981660374918665625019180688706591904","y":"544322748197457710022950388484938018344309117109747409015505319711134054941"},{"msg":"0x02b95bf729612af7a4bd97b0635b0a0774cae8f716c145d30bbfa95b29fbcffa1bd488045bc406539c906742b1f0027a512452395233667b2d49e15d4e34a5af324bae12cc0974a49d","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"7627192337387135140808795416146627087983758819107073374295508184081617595507","y":"14416007157990263185916995591605993228777168361676848637145323370081513494971"},{"msg":"0x82fd4e643319e1279c49a471b626c1bf7244795a9151487cbfe21f1651ccc24b9aab8cab3565c1d09d2fb0413d3b342e3379319db9bdb7c8745a589737f7911b3fdd130c5baf88bfe9","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"3380422288356984109363432532017472519141660432233105653148537742213610737691","y":"18098841617507578244403933395716360260566723193021942070109608819304146660129"},{"msg":"0x1298cdb7c07477fff864dffe2aaebbaa0ebb3e3abad87c243cc5f98201c9f8f0aacfdffa6bc73e0ac8fb7dd30cbeb1b85ad09cc3ccd55a7ff845f4e3923450c8400e739a57d309d7bc79","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"5047568169766210920946727502359822541172513083290618288011712384300769864251","y":"19961952651311016474453958764698405362517937437039498609953945188014346395752"},{"msg":"0xb0daae63d8657a76ce0e962807a6e3fb69bbd3a70b7218ad91be41ec286d27f2359013925e50c24ac31d64091a095eff2f1e7fdf07b3110a6216526211984279d1792d0555b622fa6df2","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"17781600162909451054701149594618106947524201366772420434390410265234787103389","y":"19355709876606237495838670245453931542520513313912340348000918911657884249589"},{"msg":"0xbce131a27b462a14adcd00a3ba68f9ca35e0eca6a0b047cec8a1ca564803ad676bc81553bf6b0d01d085786698083d47e23f3a1670488542fd4ad30cf503009565a1a10076864b48f054e2","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"20012506687290819680809192979089237344718163778772488764008399652322728341693","y":"2281089633508389625795277519507191750847086804731446977012022515134133245964"},{"msg":"0x7acb4fffa13e80aa75a2fab28fc22ec1889148f7d0a7e3421d4dae9d96e8ccb6c2af53d287d6eb74b73cb1ccd3465920578ef91c7af0e2f03ec83ef5fa8d81e80a84b6d9011667135b398f","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"313081955713480520144160524796168180583940796542770839497853053399916976688","y":"5486136469093268400234691438276046308783318971859009535195637818412118947746"},{"msg":"0xdfffbf3dfc3ad64cbfbaf3bb935038b82ce2352af08ebaa3a4ce56dff602264fb2dfacf147a0af6d48ea4ac8201d16dd8c23bf817458ac431239a3aa5da8d5b692b7c90811d37a526934a6cd","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"7153856783619771913551113420040435192249161534005834823700082364142817652815","y":"20465778955552437178209158883366390258022373773549158863642786620789255280131"},{"msg":"0x70d71a164fa1c4e6c6795150763b1bde95b67fdb18d57f97443254e8cbf7b54ed0d676510a5e337c79e754bc0e733313ea5b0221a35621a1cc6047fe1e5886f17ba9ac6b45d9e2be5ce028e9","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"6137155297762077990414982117127611882359955944582297526797677265182599880706","y":"17595637070755402177331944527619868682244544989958987330261111869952120592282"},{"msg":"0xb2b6e2aa81741864c273dad3fa96bb794de983a9d42b90970862bf11214c24b02c5d56ede6f960c9706f692fede54f9881cace74464adfd810dc2078fe0ef1575753c6270d39ab09aa896c0c92","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13663747321247130930425315694480771889962057731703982393917819734101388626054","y":"810000857486865912470485622662233526748826216845012227855289855417009266643"},{"msg":"0x22f39fe634e6fb87d227489a195c2766b1ba78e1a37a9db3817f58c42668abfae32d4671eb8715cf1338dd3209e017b60dd5ae29325cb3243e2d8d71a49d0c60396cea426ccb5011bba5d7bbf7","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"6002970257346062532935946467940858055794392438137298374877057027866272154969","y":"12568417325943165043911992725357657580134445134338233042274168024212281143510"},{"msg":"0x8ee7170503a6489626b091825038ca9f130adb5d0f3f8bf292df71b701ada9b409e156b431a2c49046b2ea811bf9206d986106d2287513f9e4660b01617f15a7c09c67f22d6e9770e23893dabfab","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"16602018123644498844367319622656492966782334813779038300226050044726019077340","y":"300904139435280953219582845599496495411982921947700069721112813380823250471"},{"msg":"0x4549dfe0399b5b6d99ece5665cc61fd660194823c10754260601c9aa43dac3fb08e6851c58e5b0a2b4db935eb073954a12c2a6800995c5ade90a636cd08d7c44efb9089e2daec4429454bdf9f3e7","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"20878084760256257044860858858756982131328335420178714421664124290101838523524","y":"5981357897386453460016780944581241433023029137658863608703439920473907740678"},{"msg":"0x8f4313e09a49c6b137061be836fe258dd027f7776152e190b6201ee375b650817531892f360aad4a1468836cf366989f7b2cbed29e4c83737d6a8f705187c09c4a67cb81412f6323bd213c1991baac","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"17797140373153955732124876301982002139352958228111548316476288160114114985975","y":"21543314621328153879647655309983583303749180380418565037165633002185675689520"},{"msg":"0xf0da485ba9970e916f0a01d34e5b36d3b3882c15f19a25bb0805ac680666fa987c36ac7a0a07ef9ab12e854be8e741e20a060758654b5214179070fe8493e4236901ff7351570354d2d4b98367fa74","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"7140251770025020911217219392346601021969051763868622961724486134950924878581","y":"2665760124230314490108817592981993187486476242912465421660251068013854746850"},{"msg":"0xeb89712b2f7839b02f8e37b363f2b99ad127fca8575474f3044c54da26973a5f2870e2de6e5cf5145f0206d5c46038e1004d69bef9b7a893aaee558a33642f29e3ceee77f5c714b4a8d684d93fcf1ff8","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"14940521125142222871445073647302353537331764306469993579744587195934889839178","y":"14411465121318462457408809215424902259936936213606220283116429355513245116113"},{"msg":"0x1044db47b4d5cd36657ae36efd7d1ebcce9b0d49d0618272f03a42fb9fbf0a603e4bd269c8c443d39974088f49e81b7c2dae6a0038f68f2b5fdec749f7534b5733265d13f96b3f14501d5cb1d87ce803","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"19076506827871342964483804635525906441557326774711569279565602888782918998626","y":"11835942334645812928160413861197040731278205489242756534150637310540297932739"},{"msg":"0xb52582c8cf8fde94198b8480d857c0c3cbfb80c9c6b071aab23318d141df6ea490d2a48f4041a5dfd93f5be774b3605c5b0f9610b52839e4453e1708a19660962291cfeeda36cf9c1ea710447c38bdf530","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"2984621210699135665658314752960794604936841587020879893430248637683821604962","y":"5264796062376396970068215774320541979355058072591117138607256072363669694573"},{"msg":"0x3336c58e6a1ee241969837a2d5976150e05280a5fad31871768413a6afdd56b7cc7e4b0b8d01395b9ec529acf02a85228b57479515cfbfd891717ab33536e483ab26b5bf118ee6aa38846145d8612f343f","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"16512988673443558916481558163231645124036163545710853153067329313478710721046","y":"2498116086499819600512657800212342526155618162282762139870099328291434415900"},{"msg":"0xc0f068997824180d16023a322a6ac0ee7cbf50051f8516be93009c32fd84ad3cb7319d6058e3943971af50645a53dad6b656c44e8922d2bd8ad350c940e76430dc3669cfca60a28f7b46305d491511f89cd5","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"19201937899087964585857377184570256335844028810545519097928451553614951938177","y":"17393714612370781320369809320831722897171589948526128945557468674887822153060"},{"msg":"0xdce6480b364f19ed26ca02d9c10321f77a61f055b02ff27e059a13ccf99187dd20aeb724c715bb13cfb9adb9ca562d6a2d5d079f7f5af0dd3de82168da771c1ae47207ae3c0dc019a847cbbd724b4162e9e6","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"6250994593023098094614930048767763860771701639962441542794472016510381022703","y":"14519684488342493092921073398095692828255745594360701545327869738201536513324"},{"msg":"0x870c18670ac83b708589443a3f82c9293338203bb73c516f5f83f9eeefec57b5e655e2a3a26f6a06e71406f06f77fbc4d6ee541f666163188f85c28e38b63b4f74ef746a060f722ed92227e09d2a75bc255d43","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"542112732911812202105027028547312924867754020530125812054090193537994254285","y":"14068799385774915488714505024903408005267453082386704418722742270767161830458"},{"msg":"0x5de768b9dc263bcb380c28109a14708cf991f84a7302c9b912a63e0805690be10f8ab209ecc3a2f32c1725dc60425a7d87487146c8aab6010866f39fab663c3764f9f74c44188714fd27be9ffdbc8d8a0ee687","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"17532666743689077510353790582709067199359432425465050927821474842350235794473","y":"15308911533631433306373046982590280773734789957170276470646036531207002154321"},{"msg":"0xfeeb84045bbb61f95a8e347fc0278cf77c1fef8b6b674c4605330b94c592aeed794475eace73e7c9420d19621c80c0c42ebde454dfe8a038f05f5d87a68957541bfa19b301be03a937b6af10e0008256bdc9de59","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"19990920292210400424551872133734332891364008395706074544696960069045000023019","y":"18580815624222870432839834348652691268782890985558799995133445147714749833608"},{"msg":"0x74a0d7628c4f090a268e693f1ff47bc45e623625c77c04303fad36697f2d2a4714b570296113fbe18a6262e784a08dcc7c85496070e084d32ac620e181702a7b3c38f5b1785d37ed65b37e1d0e86278e6ecb2af0","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13407066767428550170992485395919005670467811781916897227588748269742400346956","y":"3827379412276112084169987208923760322459377744712924419418098741402170759317"},{"msg":"0xbe1622f3064b8582fc45a922fe8865a51efc8a6dd623001411819cf0057ef62503001fbe106ee69658ed7409e8d9368a0f4a049bc646fe071802525e29ee50ecab9933b5658a644b4a1f97f6c7dec461fc56bdbd74","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"617242157956866369178334093916911770725075406386658186422041388452298832170","y":"17046741915802062302378502792258536827576588728208203085665215766140003714832"},{"msg":"0x573288339ded1fbb4c47f8a62d9322a3a288385ae3dc42add41bbc5c0c077283b56f489ee43e463cf92e62db439f9b5ba5e7c06ff1a14e52749517cc3bb7989768c968f655cbd0fc27889f2ebd951c519980062135","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"12599979870316922303755119100360756826168026236286317136036198236645741669255","y":"10793573956508412376922468391600678104953595689332223497512559995085376211815"},{"msg":"0xff8238b184ad16d35a5a1e91c8d9746f028f562e20c1aa51570dd2af72e352aa0301b32cfffbab56b97ecc943cd45f23b6b5903459a1e78665c5db9b53ee289e89622e925a7a5faf8f4a4a6db88468181cfec84747e4","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"15844769138671318706706610856404374386300491410867061063557277700972165653472","y":"18460748140681851546564929182844698900266249832756768934650542748753923838770"},{"msg":"0x8ccc8979bba97ccd7aece5ab78d6cb9185839ece5fbb25480734d2dd074c73176e5606460335bfc541686827b4a307a4e823ae5aafae2c913c6ae16dc7b88edf5db032cf01c21b75ea226ddce2bb4afc52b4297f3b3e","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"7604147088443559370565594259919030199720493995032575203731123016629193473592","y":"554233712793800516894335800499188936547941199917020295189675396731508819426"},{"msg":"0xe94187be2f8674e6a9bc963ca3c493ce3dc2a19886f42ef65b593beabf8fcd727078e9da782b9f135dd99b6dfae2aca0a0e84455e848f21de6da8c1deea3a54c6ceed5760d283f5c8f933efcf21f5609470c9420c282b9","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"9925092421075655428245366108068639020620515451239391112013773103724826563557","y":"3347331308452424141793980088217024621778372881752689201704301865029557379924"},{"msg":"0xeb49662f07592a3a9cd8c6ae517a4c66dfac1d06debc7c5882c22208d45416a859a5217bbbd050af0b6fd859798e51c8c61ac9b8994056d58da5ba0aa136822a56b138ad7d31d9a399c5c2c10f16ce2a9188913ea19477","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"14698098253298106018466140051147922160026850887959559733958787741319751546581","y":"19311654291269712517402211886886927538886440666018353800327303100782782858758"},{"msg":"0x5ccdd3a5afffbbbfc59817372060e8774c3a1159f0aca1ce242997366f500c2fc095ce20fefaa51a90ff081eb52a49d7fe133d0618d4766a65adf3a04e778b143863ccf309bf261a57dcae4a58d796955a6e4c91c5aa7de4","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"3978227256933740273311857611768225319812059592610034844179455833064520981510","y":"8254108785998910265637825412037099777751924441433428082622714100539143312798"},{"msg":"0x5ae08a4f032813101d7712d1febab4afdc8d959bcc6a62203668464d20b525cb02330c83c7a71a556b35dd34861fe00a5a5183d12d26270103d88d22b840d3d90ed30b5404b2acfaeb71c36a96b437ca47530135786aba47","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"2399800915773273570816500337570044945709958291981469088524254994002850021576","y":"13524587224225849698496656437084753022611247482124016993558104228847492865655"},{"msg":"0x2be15d6e9418fef277400eebea4e2d5a73af9d90ae4020b8ec49b3230eb027e743774e44dce06a56d1a5b4cd14c22d78509b8001738b896187b2ff1c6075af0c008b2bbd43915cf1c0b4a1bdf4f894bf4f2de63df13be6d861","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"12023871415496822551708443337548300930698498164241835397796287137925510315094","y":"4790509333499684465402862577113722794547922366943093901443214295347585226426"},{"msg":"0x2fca4280fa0bbdbd80e4c7f5ac357e95c728065c35127859a8d03f4d6b447934e8b3d0471879ad743a2795a22fa963693d47f7283db4747e3dcf825ebe38cd20928e57a52c268ab02b517265999a70289cb24da6aacfb5f5fe","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"3156337737940073738925704825730089911953645400094398240728441979127982696741","y":"8292332990099484106252075099858130393853931279051980565946170708197981998018"},{"msg":"0xd40125fa31580cef9fa18348e7800e145af9895ad857cde4f1bd7ac6579f21cb7016d089c9328f1e8ad2b252a092b08aa69502a9d66e8b4fecbff52b5a2f0c6de46445ea6e9857446764f08b855c49646ce6bb61178ca7764118","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13742364497669783191253524740711764418389952497058929643518680098270376433919","y":"17656989634315126868702815880815114491969579541038208563633243049964759928506"},{"msg":"0x145adc8465f3805480d2c805465e588a991cb896b90b4cbc3c0a240ed115b7627f822b5cd59a701dc8f17cd61f53e0cfee65775ed321c34a1eea0a6a624633fb53ced6c120f2bb6791fbe9a081363140d91185e0df70728ad151","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"8342528616238348085356841485863859886996509230931758573651179755405443206397","y":"12655279437283020946536978931581542836991947745190769610140967535986552919169"},{"msg":"0xcd3ed50eea6a45277f4fa74e628669ce6627eae30af01cdfd361dd10926223e5db79a61206ada3849e9ddf555ae16fdb5ee6c5bc94d57b545a82a0d8648f44366e7c4d6675195db8f5ef1f0456c10231ce295027cc9ea4ccb60fce","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"9873630274216731018875379397242748911697129611837048233395306440303072848816","y":"11834590038001650900417580577752055715880990037001432624530480383220716350399"},{"msg":"0xe6ea1517284d4e2b1d912335de3bc13448a539ea7862267f9361a60029ac945c084b48dc6c4edceae671778a144218acba81659f751a7b8b38f3cc40086e5ad516f7454d36f357599220cb8c03b1f97ca1591acf7c4710e6e72320","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"1558162293771364366320930408069953263927056871289597454601909698793101421325","y":"4957921013427911294156461149529976274478222765135654112552048902969653671263"},{"msg":"0x7d128c22b9cf5687940c6bd641bc925814f5cf95a126391bbcfe5a11b5e66d912d8e5d34c5d6b3ca82a2916ce257922498c2bd052f2409defd3b47b850ae5555b7d1e43f1be0c26c9600dd0d08a0df5dd208833eba22b9aeb7501df4","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"18226469040168167195485062472880866611358584479164464399068075945155995945582","y":"10576590748666229160985549243722203705177581073259228033945129994376219312230"},{"msg":"0x23876e730fca5de862d3cb42811fe9c3a4275de45f6c1f32c2bb2a20931278d2bbeb2779665d0b08b87b18bcc87afe3a4a845dc3ac968bf94edfb6d8f55e9ad0bbd8e462d7170e20d9b034e84efe5303bd6141b292f9719094529568","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"14387829311226799045660710529934173110210858869755784748022402834415663434282","y":"8084686143240348638149957696518796310312971862417283544782830700822833814376"},{"msg":"0x41ea5a110c5ec5dcd8904454ffca1c65eede3d319118b7adc8fad3b02557835310945d785fab81ee91aff0b589102245b131f37562ccdfeb3b8ee5e4b8f8aba5133be9e85045e1defa84d22447c9b32bb397bda204166ab1f7545de5bb","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"1015788175418842288345910381414714395232823160178344507168028181123182937620","y":"894397812883144523572602181272409758755522870977262973260310606894005121091"},{"msg":"0x81e3327fc05d40f3bd68952881d91e8fbc0388ab9bc1202745d0b882dd806f9b78c3d823ba503def6355ecb5efbb5b2cbeb6a1fd7e35cc2f507838cac2fab09244de63ce0f6bef22d63da904e1793cf08765542ef941bd6b02b87d94ec","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"14365002137924182318676051319709394453430671412301315729107953188308012806844","y":"18516809278513198792601658937963451301521266296416556524343266042492982999560"},{"msg":"0x502f31b5d10eb878befe30b9b6128cbfe5caa00baa0bc73225dbcf47b1ff35d18b2f2ae7c84b069f0c74ad3230ae37c67a84a60008857f8174fae8f7ddc0236b0726fa6cf16469b20aebcdfde9945e5a35ca3f85906218e7ed5ff8e4b184","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"9563921542985022392686585398764405265241352646901995174937306061727071410046","y":"16491210917164696536332138920062534303863784577462670459133556816182678505679"},{"msg":"0x51d38e89237d6af99f32b1e29d80317f287daa7142252663198c3837cd922ed9cabd9bdb5c53f98a37421df1fc63c84e5065af0ae0dd2c7a5d5486b62b9be5c8c2f777c711891a61b2dde50e4387a1b6ca79110b82e37fe415d6bf70d50e","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"19362601980298459528339073291641996917126294199925705814187857206766117562937","y":"17912471757640142424788850433760861756762440051665216706490213129226244285759"},{"msg":"0x8fb09e105940def671130a3d1a01d47450ba7288c32e047226d74d6a170763965fcdf8f07b5dfdc90989bfff3a100db4bfa0ec89a4f6a3532ff949a8bfefb31463d69c2acb9ba7a7d4afbda13de7680d76c6891beb005c264aea011c56f666","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"7669922078606788768793176003820212613811007681054338054790972975760419778320","y":"19835779260071849249870571353177942629827398866835054302592339838324560119089"},{"msg":"0xc538a9bf05ed73f069719c1ab63490240c377335724474fb22d3b8f45b51f5314f9798d03be287ecd0f2c534a7b850f64c449c91f70ef7fcbbfdc5f702cd5b272d675de60f71f97c2cb42eba7d741edf01f75c42993ff1a60e9f9fcac3d3c6","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"13837195728804066490543393040326901652434509011925252339661357718138552847467","y":"704700512805382378913640532822793937702120156153604347675508074496428151315"},{"msg":"0x6e171000fa30fb903f446f9821d758bc7f091e0f7485d9091bd31d687cf6e6db3b9ee8156f38b0e4878f3abd933a31d15a71522f82947bf27ee64dcbfa63098cefbf48b1ff3d03182f10896fc768a659566a6ff9612cb2769cd23e3a174af973","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"14255508330184173244126980644037336578114214068602464537236153140760835099891","y":"2530256413436312502777628316424711322879523411655733634204818021697484258866"},{"msg":"0x1e0183be57348dfe8dff9d03bc73164381eda193296a60e6f2f0301f9b2de9af143a9be948c2321b3cceb80d67cee26f04a0963727cfbd67a1a5dec539ac556eea6b64a4ddf86888964f028098baf57f5f1bbe74cd3083bcbe6f0c5b464a85c0","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"18299039776643577895785882845350667618938393856180081923194206551598634210511","y":"20912619928504481715999159004799605238131377405625867915612170837042320090261"},{"msg":"0xeb41df9fc9e7e722b4a3a17aa805648b07b38dd69e0242d6a2c5287b08be57f445b9fbd1b215aed7bd38c8be7721c5f560556d08de3a2e5ced25f56562a2f20525be3596f36c0e3f71d24311402ef1e85512ee45c0e42609cd8c0d9510f75554aa","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"14546622208945572371366729979224519649636153895541619307315879240748765797866","y":"3153875537963314762839342170078048302042017553293380329999789527976147491786"},{"msg":"0x52cd7ebcd2b072feb238218fd646dae5dae7c503d8b8fcff42333411f116634887c64259b17cf22cfec788366dce8692260ea32af5bb63f67465123ad0b91922144c63d726d884339935b14282da636d2bcaed71bf88c10387d58cdd14692e5b6a","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"1176262265069985821195479656924305674484772904546581175015580577618929316057","y":"21589897744224095929901227570218575523709758348500888632031848469368442851106"},{"msg":"0x74b042d1ee6d88d16e5986cf380556baba6b0ca60c1fddb6c94617fd6a7e010a463420cf68d0b7cb5240701247a2706616d50c209b36e68ddca5f041871e1793b03543a450e4c664fa0ec1dcddec10706a1e4599fbca6bca84dd38a1ca7515a316eb","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"2128314649360508552869105512422144434517875006557706497346301088476902392687","y":"18765677947732319265146729774715275893881428757395945191834353540387324273305"},{"msg":"0x73574a9977463893a0f130a37acbe73bf3bf6a6bdd11b23941f853701aa654ff71af96ec21bd817e3c3fb5b3139cdc34cd9880a78c276f755ef650f94dc3c75a8cc8fa4e99d5ccf41b1fe78567d80e7123a8b5f55d7c506a44a46cb41d0a9ba24669","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"11415459241249178528368126872245994997356208095506204358305393528178712889498","y":"12958341197956001786068849427116998576213443777055121195412423344529262287952"},{"msg":"0x0b8ca2e12286c5672d556ae0fd45b2bab34df17d287e72aa02717238ed6c0ce2f5f0f6c14a3f19cc7f1d77303a958800c3313dbde55f7fe79fd838d546242e336493ea764ae2e6b2b1cd2f308a99f6c07fcad0b34d49d3dda02509e38320bbf13a17cd","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"11824705696148767148119362713567998651479484486043574694124623309175158806244","y":"15000991757866090298152098710606023179200671643976555234534535371374933105702"},{"msg":"0x6d594d9e7c3992daca1fcf27f2a540b65f6fa15fe5a9eeba8ff762b8005146bf4c35ffc463da3208c332fa595c7272c62153f0bbbdaf5c7294acb27419ccfc5270635ec7dcd5e5b05da6b214a0f074e0dea2ace0786b59ad02f0c68e4389945efabfa2","domain":"0x6bcb0b4a1b3cb408e8052f29ce6dcc3fafc710392de5543cdb0a45381178adbd","x":"12740548981737853682728260971425314857720741436292427148752779709825660347712","y":"3029862546779462890720345512279725020652528634752349859500413156693578276937"}] \ No newline at end of file diff --git a/consensus/polybft/signer/utils.go b/consensus/polybft/signer/utils.go new file mode 100644 index 0000000000..09ff4906c0 --- /dev/null +++ b/consensus/polybft/signer/utils.go @@ -0,0 +1,48 @@ +package bls + +import ( + "crypto/rand" + "math/big" +) + +// GenerateBlsKey creates a random private and its corresponding public keys +func GenerateBlsKey() (*PrivateKey, error) { + s, err := randomK(rand.Reader) + if err != nil { + return nil, err + } + + return &PrivateKey{s: s}, nil +} + +// CreateRandomBlsKeys creates an array of random private and their corresponding public keys +func CreateRandomBlsKeys(total int) ([]*PrivateKey, error) { + blsKeys := make([]*PrivateKey, total) + + for i := 0; i < total; i++ { + blsKey, err := GenerateBlsKey() + if err != nil { + return nil, err + } + + blsKeys[i] = blsKey + } + + return blsKeys, nil +} + +// MarshalMessageToBigInt marshalls message into two big ints +// first we must convert message bytes to point and than for each coordinate we create big int +func MarshalMessageToBigInt(message []byte) ([2]*big.Int, error) { + point, err := hashToPoint(message, domain) + if err != nil { + return [2]*big.Int{}, err + } + + buf := point.Marshal() + + return [2]*big.Int{ + new(big.Int).SetBytes(buf[0:32]), + new(big.Int).SetBytes(buf[32:64]), + }, nil +} diff --git a/consensus/polybft/signer/utils_test.go b/consensus/polybft/signer/utils_test.go new file mode 100644 index 0000000000..34a8ee9eee --- /dev/null +++ b/consensus/polybft/signer/utils_test.go @@ -0,0 +1,65 @@ +package bls + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_SingleSign(t *testing.T) { + t.Parallel() + + validTestMsg, invalidTestMsg := testGenRandomBytes(t, messageSize), testGenRandomBytes(t, messageSize) + + blsKey, err := GenerateBlsKey() // structure which holds private/public key pair + require.NoError(t, err) + + // Sign valid message + signature, err := blsKey.Sign(validTestMsg) + require.NoError(t, err) + + isOk := signature.Verify(blsKey.PublicKey(), validTestMsg) + assert.True(t, isOk) + + // Verify if invalid message is signed with correct private key. Only use public key for the verification + // this should fail => isOk = false + isOk = signature.Verify(blsKey.PublicKey(), invalidTestMsg) + assert.False(t, isOk) +} + +func Test_AggregatedSign(t *testing.T) { + t.Parallel() + + validTestMsg, invalidTestMsg := testGenRandomBytes(t, messageSize), testGenRandomBytes(t, messageSize) + + keys, err := CreateRandomBlsKeys(participantsNumber) // create keys for validators + require.NoError(t, err) + + pubKeys := make([]*PublicKey, len(keys)) + + for i, key := range keys { + pubKeys[i] = key.PublicKey() + } + + var isOk bool + + signatures := Signatures{} + + // test all signatures at once + for i := 0; i < len(keys); i++ { + sign, err := keys[i].Sign(validTestMsg) + require.NoError(t, err) + + signatures = append(signatures, sign) + + // verify correctness of AggregateSignature + aggSig := signatures.Aggregate() + + isOk = aggSig.VerifyAggregated(pubKeys[:i+1], validTestMsg) + assert.True(t, isOk) + + isOk = aggSig.VerifyAggregated(pubKeys[:i+1], invalidTestMsg) + assert.False(t, isOk) + } +} diff --git a/consensus/polybft/state.go b/consensus/polybft/state.go new file mode 100644 index 0000000000..6be4b552fd --- /dev/null +++ b/consensus/polybft/state.go @@ -0,0 +1,850 @@ +package polybft + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "math/big" + "sort" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + bolt "go.etcd.io/bbolt" + + "github.com/umbracle/ethgo" +) + +/* +The client has a boltDB backed state store. The schema as of looks as follows: + +state sync events/ +|--> stateSyncEvent.Id -> *StateSyncEvent (json marshalled) + +commitments/ +|--> commitment.Message.ToIndex -> *CommitmentMessageSigned (json marshalled) + +stateSyncProofs/ +|--> stateSyncProof.StateSync.Id -> *StateSyncProof (json marshalled) + +epochs/ +|--> epochNumber + |--> hash -> []*MessageSignatures (json marshalled) + +validatorSnapshots/ +|--> epochNumber -> *AccountSet (json marshalled) + +exit events/ +|--> (id+epoch+blockNumber) -> *ExitEvent (json marshalled) + +proposer snapshot/ +|--> staticKey - only current one snapshot is preserved -> *ProposerSnapshot (json marshalled) +*/ + +var ( + // ABI + stateTransferEventABI = contractsapi.StateSender.Abi.Events["StateSynced"] + exitEventABI = contractsapi.L2StateSender.Abi.Events["L2StateSynced"] + ExitEventABIType = exitEventABI.Inputs + + // proposerSnapshotKey is a static key which is used to save latest proposer snapshot. + // (there will always be one object in bucket) + proposerSnapshotKey = []byte("proposerSnapshotKey") +) + +const ( + // validatorSnapshotLimit defines a maximum number of validator snapshots + // that can be stored in cache (both memory and db) + validatorSnapshotLimit = 100 + // numberOfSnapshotsToLeaveInMemory defines a number of validator snapshots to leave in memory + numberOfSnapshotsToLeaveInMemory = 12 + // numberOfSnapshotsToLeaveInDB defines a number of validator snapshots to leave in db + numberOfSnapshotsToLeaveInDB = 20 +) + +type exitEventNotFoundError struct { + exitID uint64 + epoch uint64 +} + +func (e *exitEventNotFoundError) Error() string { + return fmt.Sprintf("could not find any exit event that has an id: %v and epoch: %v", e.exitID, e.epoch) +} + +func decodeExitEvent(log *ethgo.Log, epoch, block uint64) (*ExitEvent, error) { + if !exitEventABI.Match(log) { + // valid case, not an exit event + return nil, nil + } + + raw, err := exitEventABI.Inputs.ParseLog(log) + if err != nil { + return nil, err + } + + eventGeneric, err := decodeEventData(raw, log, + func(id *big.Int, sender, receiver ethgo.Address, data []byte) interface{} { + return &ExitEvent{ID: id.Uint64(), + Sender: sender, + Receiver: receiver, + Data: data, + EpochNumber: epoch, + BlockNumber: block} + }) + if err != nil { + return nil, err + } + + exitEvent, ok := eventGeneric.(*ExitEvent) + if !ok { + return nil, errors.New("failed to convert event to ExitEvent instance") + } + + return exitEvent, err +} + +// decodeEventData decodes provided map of event metadata and +// creates a generic instance which is returned by eventCreator callback +func decodeEventData(eventDataMap map[string]interface{}, log *ethgo.Log, + eventCreator func(*big.Int, ethgo.Address, ethgo.Address, []byte) interface{}) (interface{}, error) { + id, ok := eventDataMap["id"].(*big.Int) + if !ok { + return nil, fmt.Errorf("failed to decode id field of log: %+v", log) + } + + sender, ok := eventDataMap["sender"].(ethgo.Address) + if !ok { + return nil, fmt.Errorf("failed to decode sender field of log: %+v", log) + } + + receiver, ok := eventDataMap["receiver"].(ethgo.Address) + if !ok { + return nil, fmt.Errorf("failed to decode receiver field of log: %+v", log) + } + + data, ok := eventDataMap["data"].([]byte) + if !ok { + return nil, fmt.Errorf("failed to decode data field of log: %+v", log) + } + + return eventCreator(id, sender, receiver, data), nil +} + +// convertLog converts types.Log to ethgo.Log +func convertLog(log *types.Log) *ethgo.Log { + l := ðgo.Log{ + Address: ethgo.Address(log.Address), + Data: log.Data, + Topics: make([]ethgo.Hash, len(log.Topics)), + } + + for i, topic := range log.Topics { + l.Topics[i] = ethgo.Hash(topic) + } + + return l +} + +// ExitEvent is an event emitted by Exit contract +type ExitEvent struct { + // ID is the decoded 'index' field from the event + ID uint64 `abi:"id"` + // Sender is the decoded 'sender' field from the event + Sender ethgo.Address `abi:"sender"` + // Receiver is the decoded 'receiver' field from the event + Receiver ethgo.Address `abi:"receiver"` + // Data is the decoded 'data' field from the event + Data []byte `abi:"data"` + // EpochNumber is the epoch number in which exit event was added + EpochNumber uint64 `abi:"-"` + // BlockNumber is the block in which exit event was added + BlockNumber uint64 `abi:"-"` +} + +// MessageSignature encapsulates sender identifier and its signature +type MessageSignature struct { + // Signer of the vote + From string + // Signature of the message + Signature []byte +} + +// TransportMessage represents the payload which is gossiped across the network +type TransportMessage struct { + // Hash is encoded data + Hash []byte + // Message signature + Signature []byte + // Node identifier + NodeID string + // Number of epoch + EpochNumber uint64 +} + +func (t *TransportMessage) ToSignature() *MessageSignature { + return &MessageSignature{ + Signature: t.Signature, + From: t.NodeID, + } +} + +var ( + // bucket to store rootchain bridge events + syncStateEventsBucket = []byte("stateSyncEvents") + // bucket to store exit contract events + exitEventsBucket = []byte("exitEvent") + // bucket to store commitments + commitmentsBucket = []byte("commitments") + // bucket to store state sync proofs + stateSyncProofsBucket = []byte("stateSyncProofs") + // bucket to store epochs and all its nested buckets (message votes and message pool events) + epochsBucket = []byte("epochs") + // bucket to store message votes (signatures) + messageVotesBucket = []byte("votes") + // bucket to store validator snapshots + validatorSnapshotsBucket = []byte("validatorSnapshots") + // bucket to store proposer calculator snapshot + proposerCalcSnapshotBucket = []byte("proposerCalculatorSnapshot") + // array of all parent buckets + parentBuckets = [][]byte{syncStateEventsBucket, exitEventsBucket, commitmentsBucket, stateSyncProofsBucket, + epochsBucket, validatorSnapshotsBucket, proposerCalcSnapshotBucket} + // errNotEnoughStateSyncs error message + errNotEnoughStateSyncs = errors.New("there is either a gap or not enough sync events") + // errCommitmentNotBuilt error message + errCommitmentNotBuilt = errors.New("there is no built commitment to register") + // errNoCommitmentForStateSync error message + errNoCommitmentForStateSync = errors.New("no commitment found for given state sync event") +) + +// State represents a persistence layer which persists consensus data off-chain +type State struct { + db *bolt.DB + logger hclog.Logger + close chan struct{} +} + +func newState(path string, logger hclog.Logger, closeCh chan struct{}) (*State, error) { + db, err := bolt.Open(path, 0666, nil) + if err != nil { + return nil, err + } + + if err = initMainDBBuckets(db); err != nil { + return nil, err + } + + state := &State{ + db: db, + logger: logger.Named("state"), + close: closeCh, + } + + return state, nil +} + +// initMainDBBuckets creates predefined buckets in bolt database if they don't exist already. +func initMainDBBuckets(db *bolt.DB) error { + // init the buckets + err := db.Update(func(tx *bolt.Tx) error { + for _, bucket := range parentBuckets { + if _, err := tx.CreateBucketIfNotExists(bucket); err != nil { + return err + } + } + + return nil + }) + + return err +} + +// insertValidatorSnapshot inserts a validator snapshot for the given block to its bucket in db +func (s *State) insertValidatorSnapshot(validatorSnapshot *validatorSnapshot) error { + return s.db.Update(func(tx *bolt.Tx) error { + raw, err := json.Marshal(validatorSnapshot) + if err != nil { + return err + } + + return tx.Bucket(validatorSnapshotsBucket).Put(itob(validatorSnapshot.Epoch), raw) + }) +} + +// getValidatorSnapshot queries the validator snapshot for given block from db +func (s *State) getValidatorSnapshot(epoch uint64) (*validatorSnapshot, error) { + var validatorSnapshot *validatorSnapshot + + err := s.db.View(func(tx *bolt.Tx) error { + v := tx.Bucket(validatorSnapshotsBucket).Get(itob(epoch)) + if v != nil { + return json.Unmarshal(v, &validatorSnapshot) + } + + return nil + }) + + return validatorSnapshot, err +} + +// getLastSnapshot returns the last snapshot saved in db +// since they are stored by epoch number (uint64), they are sequentially stored, +// so the latest epoch will be the last snapshot in db +func (s *State) getLastSnapshot() (*validatorSnapshot, error) { + var snapshot *validatorSnapshot + + err := s.db.View(func(tx *bolt.Tx) error { + c := tx.Bucket(validatorSnapshotsBucket).Cursor() + k, v := c.Last() + if k == nil { + // we have no snapshots in db + return nil + } + + return json.Unmarshal(v, &snapshot) + }) + + return snapshot, err +} + +// list iterates through all events in events bucket in db, un-marshals them, and returns as array +func (s *State) list() ([]*contractsapi.StateSyncedEvent, error) { + events := []*contractsapi.StateSyncedEvent{} + + err := s.db.View(func(tx *bolt.Tx) error { + return tx.Bucket(syncStateEventsBucket).ForEach(func(k, v []byte) error { + var event *contractsapi.StateSyncedEvent + if err := json.Unmarshal(v, &event); err != nil { + return err + } + events = append(events, event) + + return nil + }) + }) + if err != nil { + return nil, err + } + + return events, nil +} + +// insertExitEvents inserts a slice of exit events to exit event bucket in bolt db +func (s *State) insertExitEvents(exitEvents []*ExitEvent) error { + if len(exitEvents) == 0 { + // small optimization + return nil + } + + return s.db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket(exitEventsBucket) + for i := 0; i < len(exitEvents); i++ { + if err := insertExitEventToBucket(bucket, exitEvents[i]); err != nil { + return err + } + } + + return nil + }) +} + +// insertExitEvent inserts a new exit event to exit event bucket in bolt db +func (s *State) insertExitEvent(event *ExitEvent) error { + return s.db.Update(func(tx *bolt.Tx) error { + return insertExitEventToBucket(tx.Bucket(exitEventsBucket), event) + }) +} + +// insertExitEventToBucket inserts exit event to exit event bucket +func insertExitEventToBucket(bucket *bolt.Bucket, exitEvent *ExitEvent) error { + raw, err := json.Marshal(exitEvent) + if err != nil { + return err + } + + return bucket.Put(bytes.Join([][]byte{itob(exitEvent.EpochNumber), + itob(exitEvent.ID), itob(exitEvent.BlockNumber)}, nil), raw) +} + +// getExitEvent returns exit event with given id, which happened in given epoch and given block number +func (s *State) getExitEvent(exitEventID, epoch uint64) (*ExitEvent, error) { + var exitEvent *ExitEvent + + err := s.db.View(func(tx *bolt.Tx) error { + bucket := tx.Bucket(exitEventsBucket) + + key := bytes.Join([][]byte{itob(epoch), itob(exitEventID)}, nil) + k, v := bucket.Cursor().Seek(key) + + if bytes.HasPrefix(k, key) == false || v == nil { + return &exitEventNotFoundError{ + exitID: exitEventID, + epoch: epoch, + } + } + + return json.Unmarshal(v, &exitEvent) + }) + + return exitEvent, err +} + +// getExitEventsByEpoch returns all exit events that happened in the given epoch +func (s *State) getExitEventsByEpoch(epoch uint64) ([]*ExitEvent, error) { + return s.getExitEvents(epoch, func(exitEvent *ExitEvent) bool { + return exitEvent.EpochNumber == epoch + }) +} + +// getExitEventsForProof returns all exit events that happened in and prior to the given checkpoint block number +// with respect to the epoch in which block is added +func (s *State) getExitEventsForProof(epoch, checkpointBlock uint64) ([]*ExitEvent, error) { + return s.getExitEvents(epoch, func(exitEvent *ExitEvent) bool { + return exitEvent.EpochNumber == epoch && exitEvent.BlockNumber <= checkpointBlock + }) +} + +// getExitEvents returns exit events for given epoch and provided filter +func (s *State) getExitEvents(epoch uint64, filter func(exitEvent *ExitEvent) bool) ([]*ExitEvent, error) { + var events []*ExitEvent + + err := s.db.View(func(tx *bolt.Tx) error { + c := tx.Bucket(exitEventsBucket).Cursor() + prefix := itob(epoch) + + for k, v := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, v = c.Next() { + var event *ExitEvent + if err := json.Unmarshal(v, &event); err != nil { + return err + } + + if filter(event) { + events = append(events, event) + } + } + + return nil + }) + + // enforce sequential order + sort.Slice(events, func(i, j int) bool { + return events[i].ID < events[j].ID + }) + + return events, err +} + +// insertStateSyncEvent inserts a new state sync event to state event bucket in db +func (s *State) insertStateSyncEvent(event *contractsapi.StateSyncedEvent) error { + return s.db.Update(func(tx *bolt.Tx) error { + raw, err := json.Marshal(event) + if err != nil { + return err + } + + bucket := tx.Bucket(syncStateEventsBucket) + + return bucket.Put(itob(event.ID.Uint64()), raw) + }) +} + +// getStateSyncEventsForCommitment returns state sync events for commitment +func (s *State) getStateSyncEventsForCommitment(fromIndex, toIndex uint64) ([]*contractsapi.StateSyncedEvent, error) { + var events []*contractsapi.StateSyncedEvent + + err := s.db.View(func(tx *bolt.Tx) error { + bucket := tx.Bucket(syncStateEventsBucket) + for i := fromIndex; i <= toIndex; i++ { + v := bucket.Get(itob(i)) + if v == nil { + return errNotEnoughStateSyncs + } + + var event *contractsapi.StateSyncedEvent + if err := json.Unmarshal(v, &event); err != nil { + return err + } + + events = append(events, event) + } + + return nil + }) + + return events, err +} + +// insertEpoch inserts a new epoch to db with its meta data +func (s *State) insertEpoch(epoch uint64) error { + return s.db.Update(func(tx *bolt.Tx) error { + epochBucket, err := tx.Bucket(epochsBucket).CreateBucketIfNotExists(itob(epoch)) + if err != nil { + return err + } + _, err = epochBucket.CreateBucketIfNotExists(messageVotesBucket) + + return err + }) +} + +// isEpochInserted checks if given epoch is present in db +func (s *State) isEpochInserted(epoch uint64) bool { + return s.db.View(func(tx *bolt.Tx) error { + _, err := getEpochBucket(tx, epoch) + + return err + }) == nil +} + +// insertCommitmentMessage inserts signed commitment to db +func (s *State) insertCommitmentMessage(commitment *CommitmentMessageSigned) error { + return s.db.Update(func(tx *bolt.Tx) error { + raw, err := json.Marshal(commitment) + if err != nil { + return err + } + + if err := tx.Bucket(commitmentsBucket).Put(itob(commitment.Message.EndID.Uint64()), raw); err != nil { + return err + } + + return nil + }) +} + +// getCommitmentMessage queries the signed commitment from the db +func (s *State) getCommitmentMessage(toIndex uint64) (*CommitmentMessageSigned, error) { + var commitment *CommitmentMessageSigned + + err := s.db.View(func(tx *bolt.Tx) error { + raw := tx.Bucket(commitmentsBucket).Get(itob(toIndex)) + if raw == nil { + return nil + } + + return json.Unmarshal(raw, &commitment) + }) + + return commitment, err +} + +// insertStateSyncProofs inserts the provided state sync proofs to db +func (s *State) insertStateSyncProofs(stateSyncProof []*StateSyncProof) error { + return s.db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket(stateSyncProofsBucket) + for _, ssp := range stateSyncProof { + raw, err := json.Marshal(ssp) + if err != nil { + return err + } + + if err := bucket.Put(itob(ssp.StateSync.ID.Uint64()), raw); err != nil { + return err + } + } + + return nil + }) +} + +// getStateSyncProof gets state sync proof that are not executed +func (s *State) getStateSyncProof(stateSyncID uint64) (*StateSyncProof, error) { + var ssp *StateSyncProof + + err := s.db.View(func(tx *bolt.Tx) error { + if v := tx.Bucket(stateSyncProofsBucket).Get(itob(stateSyncID)); v != nil { + if err := json.Unmarshal(v, &ssp); err != nil { + return err + } + } + + return nil + }) + + return ssp, err +} + +// getCommitmentForStateSync returns the commitment that contains given state sync event if it exists +func (s *State) getCommitmentForStateSync(stateSyncID uint64) (*CommitmentMessageSigned, error) { + var commitment *CommitmentMessageSigned + + err := s.db.View(func(tx *bolt.Tx) error { + c := tx.Bucket(commitmentsBucket).Cursor() + + k, v := c.Seek(itob(stateSyncID)) + if k == nil { + return errNoCommitmentForStateSync + } + + if err := json.Unmarshal(v, &commitment); err != nil { + return err + } + + if !commitment.ContainsStateSync(stateSyncID) { + return errNoCommitmentForStateSync + } + + return nil + }) + + return commitment, err +} + +// insertMessageVote inserts given vote to signatures bucket of given epoch +func (s *State) insertMessageVote(epoch uint64, key []byte, vote *MessageSignature) (int, error) { + var numSignatures int + + err := s.db.Update(func(tx *bolt.Tx) error { + signatures, err := s.getMessageVotesLocked(tx, epoch, key) + if err != nil { + return err + } + + // check if the signature has already being included + for _, sigs := range signatures { + if sigs.From == vote.From { + numSignatures = len(signatures) + + return nil + } + } + + if signatures == nil { + signatures = []*MessageSignature{vote} + } else { + signatures = append(signatures, vote) + } + numSignatures = len(signatures) + + raw, err := json.Marshal(signatures) + if err != nil { + return err + } + + bucket, err := getNestedBucketInEpoch(tx, epoch, messageVotesBucket) + if err != nil { + return err + } + + if err := bucket.Put(key, raw); err != nil { + return err + } + + return nil + }) + + if err != nil { + return 0, err + } + + return numSignatures, nil +} + +// getMessageVotes gets all signatures from db associated with given epoch and hash +func (s *State) getMessageVotes(epoch uint64, hash []byte) ([]*MessageSignature, error) { + var signatures []*MessageSignature + + err := s.db.View(func(tx *bolt.Tx) error { + res, err := s.getMessageVotesLocked(tx, epoch, hash) + if err != nil { + return err + } + signatures = res + + return nil + }) + + if err != nil { + return nil, err + } + + return signatures, nil +} + +// getMessageVotesLocked gets all signatures from db associated with given epoch and hash +func (s *State) getMessageVotesLocked(tx *bolt.Tx, epoch uint64, hash []byte) ([]*MessageSignature, error) { + bucket, err := getNestedBucketInEpoch(tx, epoch, messageVotesBucket) + if err != nil { + return nil, err + } + + v := bucket.Get(hash) + if v == nil { + return nil, nil + } + + var signatures []*MessageSignature + if err := json.Unmarshal(v, &signatures); err != nil { + return nil, err + } + + return signatures, nil +} + +// getNestedBucketInEpoch returns a nested (child) bucket from db associated with given epoch +func getNestedBucketInEpoch(tx *bolt.Tx, epoch uint64, bucketKey []byte) (*bolt.Bucket, error) { + epochBucket, err := getEpochBucket(tx, epoch) + if err != nil { + return nil, err + } + + bucket := epochBucket.Bucket(bucketKey) + + if epochBucket == nil { + return nil, fmt.Errorf("could not find %v bucket for epoch: %v", string(bucketKey), epoch) + } + + return bucket, nil +} + +// getEpochBucket returns bucket from db associated with given epoch +func getEpochBucket(tx *bolt.Tx, epoch uint64) (*bolt.Bucket, error) { + epochBucket := tx.Bucket(epochsBucket).Bucket(itob(epoch)) + if epochBucket == nil { + return nil, fmt.Errorf("could not find bucket for epoch: %v", epoch) + } + + return epochBucket, nil +} + +// cleanEpochsFromDB cleans epoch buckets from db +func (s *State) cleanEpochsFromDB() error { + return s.db.Update(func(tx *bolt.Tx) error { + if err := tx.DeleteBucket(epochsBucket); err != nil { + return err + } + _, err := tx.CreateBucket(epochsBucket) + + return err + }) +} + +// cleanValidatorSnapshotsFromDB cleans the validator snapshots bucket if a limit is reached, +// but it leaves the latest (n) number of snapshots +func (s *State) cleanValidatorSnapshotsFromDB(epoch uint64) error { + return s.db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket(validatorSnapshotsBucket) + + // paired list + keys := make([][]byte, 0) + values := make([][]byte, 0) + for i := 0; i < numberOfSnapshotsToLeaveInDB; i++ { // exclude the last inserted we already appended + key := itob(epoch) + value := bucket.Get(key) + if value == nil { + continue + } + keys = append(keys, key) + values = append(values, value) + epoch-- + } + + // removing an entire bucket is much faster than removing all keys + // look at thread https://github.com/boltdb/bolt/issues/667 + err := tx.DeleteBucket(validatorSnapshotsBucket) + if err != nil { + return err + } + + bucket, err = tx.CreateBucket(validatorSnapshotsBucket) + if err != nil { + return err + } + + // we start the loop in reverse so that the oldest of snapshots get inserted first in db + for i := len(keys) - 1; i >= 0; i-- { + if err := bucket.Put(keys[i], values[i]); err != nil { + return err + } + } + + return nil + }) +} + +// removeAllValidatorSnapshots drops a validator snapshot bucket and re-creates it in bolt database +func (s *State) removeAllValidatorSnapshots() error { + return s.db.Update(func(tx *bolt.Tx) error { + // removing an entire bucket is much faster than removing all keys + // look at thread https://github.com/boltdb/bolt/issues/667 + err := tx.DeleteBucket(validatorSnapshotsBucket) + if err != nil { + return err + } + + _, err = tx.CreateBucket(validatorSnapshotsBucket) + if err != nil { + return err + } + + return nil + }) +} + +// epochsDBStats returns stats of epochs bucket in db +func (s *State) epochsDBStats() *bolt.BucketStats { + return s.bucketStats(epochsBucket) +} + +// validatorSnapshotsDBStats returns stats of validators snapshot bucket in db +func (s *State) validatorSnapshotsDBStats() *bolt.BucketStats { + return s.bucketStats(validatorSnapshotsBucket) +} + +// bucketStats returns stats for the given bucket in db +func (s *State) bucketStats(bucketName []byte) *bolt.BucketStats { + var stats *bolt.BucketStats + + err := s.db.View(func(tx *bolt.Tx) error { + s := tx.Bucket(bucketName).Stats() + stats = &s + + return nil + }) + + if err != nil { + s.logger.Error("Cannot check bucket stats", "Bucket name", string(bucketName), "Error", err) + } + + return stats +} + +// getProposerSnapshot gets latest proposer snapshot +func (s *State) getProposerSnapshot() (*ProposerSnapshot, error) { + var snapshot *ProposerSnapshot + + err := s.db.View(func(tx *bolt.Tx) error { + value := tx.Bucket(proposerCalcSnapshotBucket).Get(proposerSnapshotKey) + if value == nil { + return nil + } + + return json.Unmarshal(value, &snapshot) + }) + + return snapshot, err +} + +// writeProposerSnapshot writes proposer snapshot +func (s *State) writeProposerSnapshot(snapshot *ProposerSnapshot) error { + raw, err := json.Marshal(snapshot) + if err != nil { + return err + } + + return s.db.Update(func(tx *bolt.Tx) error { + return tx.Bucket(proposerCalcSnapshotBucket).Put(proposerSnapshotKey, raw) + }) +} + +func itob(v uint64) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, v) + + return b +} + +func itou(v []byte) uint64 { + return binary.BigEndian.Uint64(v) +} diff --git a/consensus/polybft/state_stats.go b/consensus/polybft/state_stats.go new file mode 100644 index 0000000000..02ae335e0c --- /dev/null +++ b/consensus/polybft/state_stats.go @@ -0,0 +1,156 @@ +package polybft + +import ( + "time" + + "github.com/armon/go-metrics" + "github.com/prometheus/client_golang/prometheus" +) + +// startStatsReleasing starts the process that releases BoltDB stats into prometheus periodically. +func (s *State) startStatsReleasing() { + const ( + statUpdatePeriod = 10 * time.Second + dbSubsystem = "db" + txSubsystem = "tx" + namespace = "polybft_state" + ) + + // Grab the initial stats. + prev := s.db.Stats() + + // Initialize ticker in order to send stats once a statUpdatePeriod + ticker := time.NewTicker(statUpdatePeriod) + + // Stop ticker (stats releasing basically) when receiving the closing signal + go func() { + <-s.close + ticker.Stop() + }() + + for range ticker.C { + // Grab the current stats and diff them. + stats := s.db.Stats() + diff := stats.Sub(&prev) + + // Freelist stats + { + // Update total number of free pages on the freelist + metrics.SetGauge( + []string{prometheus.BuildFQName(namespace, dbSubsystem, "freelist_free_pages")}, + float32(diff.FreePageN), + ) + + // Update total number of pending pages on the freelist + metrics.SetGauge( + []string{prometheus.BuildFQName(namespace, dbSubsystem, "freelist_pending_pages")}, + float32(diff.PendingPageN), + ) + + // Update total bytes allocated in free pages + metrics.SetGauge( + []string{prometheus.BuildFQName(namespace, dbSubsystem, "freelist_free_page_allocated_bytes")}, + float32(diff.FreeAlloc), + ) + + // Update total bytes used by the freelist + metrics.SetGauge( + []string{prometheus.BuildFQName(namespace, dbSubsystem, "freelist_in_use_bytes")}, + float32(diff.FreelistInuse), + ) + } + + // Transaction stats + { + // Update total number of started read transactions + metrics.IncrCounter( + []string{prometheus.BuildFQName(namespace, dbSubsystem, "read_tx_total")}, + float32(diff.TxN), + ) + + // Update number of currently open read transactions + metrics.SetGauge( + []string{prometheus.BuildFQName(namespace, dbSubsystem, "open_read_tx")}, + float32(diff.OpenTxN), + ) + } + + // Global, ongoing stats + { + // Update number of page allocations + metrics.IncrCounter( + []string{prometheus.BuildFQName(namespace, txSubsystem, "pages_allocated_total")}, + float32(diff.TxStats.PageCount), + ) + + // Update total bytes allocated + metrics.IncrCounter( + []string{prometheus.BuildFQName(namespace, txSubsystem, "pages_allocated_bytes_total")}, + float32(diff.TxStats.PageAlloc), + ) + + // Update number of cursors created + metrics.IncrCounter( + []string{prometheus.BuildFQName(namespace, txSubsystem, "cursors_total")}, + float32(diff.TxStats.CursorCount), + ) + + // Update number of node allocations + metrics.IncrCounter( + []string{prometheus.BuildFQName(namespace, txSubsystem, "nodes_allocated_total")}, + float32(diff.TxStats.NodeCount), + ) + + // Update number of node dereferences + metrics.IncrCounter( + []string{prometheus.BuildFQName(namespace, txSubsystem, "nodes_dereferenced_total")}, + float32(diff.TxStats.NodeDeref), + ) + + // Update number of node rebalances + metrics.IncrCounter( + []string{prometheus.BuildFQName(namespace, txSubsystem, "node_rebalances_total")}, + float32(diff.TxStats.Rebalance), + ) + + // Update total time spent rebalancing + metrics.IncrCounter( + []string{prometheus.BuildFQName(namespace, txSubsystem, "node_rebalance_seconds_total")}, + float32(diff.TxStats.RebalanceTime.Seconds()), + ) + + // Update number of nodes split + metrics.IncrCounter( + []string{prometheus.BuildFQName(namespace, txSubsystem, "nodes_split_total")}, + float32(diff.TxStats.Split), + ) + + // Update number of nodes spilled + metrics.IncrCounter( + []string{prometheus.BuildFQName(namespace, txSubsystem, "nodes_spilled_total")}, + float32(diff.TxStats.Spill), + ) + + // Update total time spent spilling + metrics.IncrCounter( + []string{prometheus.BuildFQName(namespace, txSubsystem, "nodes_spilled_seconds_total")}, + float32(diff.TxStats.SpillTime.Seconds()), + ) + + // Update number of writes performed + metrics.IncrCounter( + []string{prometheus.BuildFQName(namespace, txSubsystem, "writes_total")}, + float32(diff.TxStats.Write), + ) + + // Update total time spent writing to disk + metrics.IncrCounter( + []string{prometheus.BuildFQName(namespace, txSubsystem, "write_seconds_total")}, + float32(diff.TxStats.WriteTime), + ) + } + + // Save stats for the next loop. + prev = stats + } +} diff --git a/consensus/polybft/state_sync_manager.go b/consensus/polybft/state_sync_manager.go new file mode 100644 index 0000000000..89d619738a --- /dev/null +++ b/consensus/polybft/state_sync_manager.go @@ -0,0 +1,554 @@ +package polybft + +import ( + "context" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "path" + "sync" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/bitmap" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + polybftProto "github.com/0xPolygon/polygon-edge/consensus/polybft/proto" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" + "github.com/0xPolygon/polygon-edge/tracker" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/umbracle/ethgo" + "google.golang.org/protobuf/proto" +) + +const ( + // minimum number of stateSyncEvents that a commitment can have + // (minimum number is 2 because smart contract expects that the merkle tree has at least two leaves) + minCommitmentSize = 2 +) + +type StateSyncProof struct { + Proof []types.Hash + StateSync *contractsapi.StateSyncedEvent +} + +// StateSyncManager is an interface that defines functions for state sync workflow +type StateSyncManager interface { + Init() error + Close() + Commitment() (*CommitmentMessageSigned, error) + GetStateSyncProof(stateSyncID uint64) (types.Proof, error) + PostBlock(req *PostBlockRequest) error + PostEpoch(req *PostEpochRequest) error +} + +var _ StateSyncManager = (*dummyStateSyncManager)(nil) + +// dummyStateSyncManager is used when bridge is not enabled +type dummyStateSyncManager struct{} + +func (n *dummyStateSyncManager) Init() error { return nil } +func (n *dummyStateSyncManager) Close() {} +func (n *dummyStateSyncManager) Commitment() (*CommitmentMessageSigned, error) { return nil, nil } +func (n *dummyStateSyncManager) PostBlock(req *PostBlockRequest) error { return nil } +func (n *dummyStateSyncManager) PostEpoch(req *PostEpochRequest) error { return nil } +func (n *dummyStateSyncManager) GetStateSyncProof(stateSyncID uint64) (types.Proof, error) { + return types.Proof{}, nil +} + +// stateSyncConfig holds the configuration data of state sync manager +type stateSyncConfig struct { + stateSenderAddr types.Address + jsonrpcAddr string + dataDir string + topic topic + key *wallet.Key + maxCommitmentSize uint64 +} + +var _ StateSyncManager = (*stateSyncManager)(nil) + +// stateSyncManager is a struct that manages the workflow of +// saving and querying state sync events, and creating, and submitting new commitments +type stateSyncManager struct { + logger hclog.Logger + state *State + + config *stateSyncConfig + closeCh chan struct{} + + // per epoch fields + lock sync.RWMutex + pendingCommitments []*PendingCommitment + validatorSet ValidatorSet + epoch uint64 + nextCommittedIndex uint64 +} + +// topic is an interface for p2p message gossiping +type topic interface { + Publish(obj proto.Message) error + Subscribe(handler func(obj interface{}, from peer.ID)) error +} + +// NewStateSyncManager creates a new instance of state sync manager +func NewStateSyncManager(logger hclog.Logger, state *State, config *stateSyncConfig) (*stateSyncManager, error) { + s := &stateSyncManager{ + logger: logger.Named("state-sync-manager"), + state: state, + config: config, + closeCh: make(chan struct{}), + } + + return s, nil +} + +// Init subscribes to bridge topics (getting votes) and start the event tracker routine +func (s *stateSyncManager) Init() error { + if err := s.initTracker(); err != nil { + return fmt.Errorf("failed to init event tracker. Error: %w", err) + } + + if err := s.initTransport(); err != nil { + return fmt.Errorf("failed to initialize state sync transport layer. Error: %w", err) + } + + return nil +} + +func (s *stateSyncManager) Close() { + close(s.closeCh) +} + +// initTracker starts a new event tracker (to receive new state sync events) +func (s *stateSyncManager) initTracker() error { + ctx, cancelFn := context.WithCancel(context.Background()) + + tracker := tracker.NewEventTracker( + path.Join(s.config.dataDir, "/deposit.db"), + s.config.jsonrpcAddr, + ethgo.Address(s.config.stateSenderAddr), + s, + s.logger) + + go func() { + <-s.closeCh + cancelFn() + }() + + return tracker.Start(ctx) +} + +// initTransport subscribes to bridge topics (getting votes for commitments) +func (s *stateSyncManager) initTransport() error { + return s.config.topic.Subscribe(func(obj interface{}, _ peer.ID) { + msg, ok := obj.(*polybftProto.TransportMessage) + if !ok { + s.logger.Warn("failed to deliver vote, invalid msg", "obj", obj) + + return + } + + var transportMsg *TransportMessage + + if err := json.Unmarshal(msg.Data, &transportMsg); err != nil { + s.logger.Warn("failed to deliver vote", "error", err) + + return + } + + if err := s.saveVote(transportMsg); err != nil { + s.logger.Warn("failed to deliver vote", "error", err) + } + }) +} + +// saveVote saves the gotten vote to boltDb for later quorum check and signature aggregation +func (s *stateSyncManager) saveVote(msg *TransportMessage) error { + s.lock.RLock() + epoch := s.epoch + valSet := s.validatorSet + s.lock.RUnlock() + + if valSet == nil || msg.EpochNumber < epoch { + // Epoch metadata is undefined or received message for some of the older epochs + return nil + } + + if !valSet.Includes(types.StringToAddress(msg.NodeID)) { + return fmt.Errorf("validator is not among the active validator set") + } + + msgVote := &MessageSignature{ + From: msg.NodeID, + Signature: msg.Signature, + } + + numSignatures, err := s.state.insertMessageVote(msg.EpochNumber, msg.Hash, msgVote) + if err != nil { + return fmt.Errorf("error inserting message vote: %w", err) + } + + s.logger.Info( + "deliver message", + "hash", hex.EncodeToString(msg.Hash), + "sender", msg.NodeID, + "signatures", numSignatures, + ) + + return nil +} + +// AddLog saves the received log from event tracker if it matches a state sync event ABI +func (s *stateSyncManager) AddLog(eventLog *ethgo.Log) { + if !stateTransferEventABI.Match(eventLog) { + return + } + + s.logger.Info( + "Add State sync event", + "block", eventLog.BlockNumber, + "hash", eventLog.TransactionHash, + "index", eventLog.LogIndex, + ) + + event := &contractsapi.StateSyncedEvent{} + + if err := event.ParseLog(eventLog); err != nil { + s.logger.Error("could not decode state sync event", "err", err) + + return + } + + if err := s.state.insertStateSyncEvent(event); err != nil { + s.logger.Error("could not save state sync event to boltDb", "err", err) + + return + } + + if err := s.buildCommitment(); err != nil { + s.logger.Error("could not build a commitment on arrival of new state sync", "err", err, "stateSyncID", event.ID) + } +} + +// Commitment returns a commitment to be submitted if there is a pending commitment with quorum +func (s *stateSyncManager) Commitment() (*CommitmentMessageSigned, error) { + s.lock.RLock() + defer s.lock.RUnlock() + + var largestCommitment *CommitmentMessageSigned + + // we start from the end, since last pending commitment is the largest one + for i := len(s.pendingCommitments) - 1; i >= 0; i-- { + commitment := s.pendingCommitments[i] + aggregatedSignature, publicKeys, err := s.getAggSignatureForCommitmentMessage(commitment) + + if err != nil { + if errors.Is(err, errQuorumNotReached) { + // a valid case, commitment has no quorum, we should not return an error + s.logger.Debug("can not submit a commitment, quorum not reached", + "from", commitment.StartID.Uint64(), + "to", commitment.EndID.Uint64()) + + continue + } + + return nil, err + } + + largestCommitment = &CommitmentMessageSigned{ + Message: commitment.StateSyncCommitment, + AggSignature: aggregatedSignature, + PublicKeys: publicKeys, + } + + break + } + + return largestCommitment, nil +} + +// getAggSignatureForCommitmentMessage checks if pending commitment has quorum, +// and if it does, aggregates the signatures +func (s *stateSyncManager) getAggSignatureForCommitmentMessage( + commitment *PendingCommitment) (Signature, [][]byte, error) { + validatorSet := s.validatorSet + + validatorAddrToIndex := make(map[string]int, validatorSet.Len()) + validatorsMetadata := validatorSet.Accounts() + + for i, validator := range validatorsMetadata { + validatorAddrToIndex[validator.Address.String()] = i + } + + commitmentHash, err := commitment.Hash() + if err != nil { + return Signature{}, nil, err + } + + // get all the votes from the database for this commitment + votes, err := s.state.getMessageVotes(commitment.Epoch, commitmentHash.Bytes()) + if err != nil { + return Signature{}, nil, err + } + + var signatures bls.Signatures + + publicKeys := make([][]byte, 0) + bitmap := bitmap.Bitmap{} + signers := make(map[types.Address]struct{}, 0) + + for _, vote := range votes { + index, exists := validatorAddrToIndex[vote.From] + if !exists { + continue // don't count this vote, because it does not belong to validator + } + + signature, err := bls.UnmarshalSignature(vote.Signature) + if err != nil { + return Signature{}, nil, err + } + + bitmap.Set(uint64(index)) + + signatures = append(signatures, signature) + publicKeys = append(publicKeys, validatorsMetadata[index].BlsKey.Marshal()) + signers[types.StringToAddress(vote.From)] = struct{}{} + } + + if !validatorSet.HasQuorum(signers) { + return Signature{}, nil, errQuorumNotReached + } + + aggregatedSignature, err := signatures.Aggregate().Marshal() + if err != nil { + return Signature{}, nil, err + } + + result := Signature{ + AggregatedSignature: aggregatedSignature, + Bitmap: bitmap, + } + + return result, publicKeys, nil +} + +// PostEpoch notifies the state sync manager that an epoch has changed, +// so that it can discard any previous epoch commitments, and build a new one (since validator set changed) +func (s *stateSyncManager) PostEpoch(req *PostEpochRequest) error { + s.lock.Lock() + + s.pendingCommitments = nil + s.validatorSet = req.ValidatorSet + s.epoch = req.NewEpochID + + // build a new commitment at the end of the epoch + nextCommittedIndex, err := req.SystemState.GetNextCommittedIndex() + if err != nil { + s.lock.Unlock() + + return err + } + + s.nextCommittedIndex = nextCommittedIndex + + s.lock.Unlock() + + return s.buildCommitment() +} + +// PostBlock notifies state sync manager that a block was finalized, +// so that it can build state sync proofs if a block has a commitment submission transaction +func (s *stateSyncManager) PostBlock(req *PostBlockRequest) error { + commitment, err := getCommitmentMessageSignedTx(req.FullBlock.Block.Transactions) + if err != nil { + return err + } + + // no commitment message -> this is not end of epoch block + if commitment == nil { + return nil + } + + if err := s.state.insertCommitmentMessage(commitment); err != nil { + return fmt.Errorf("insert commitment message error: %w", err) + } + + if err := s.buildProofs(commitment.Message); err != nil { + return fmt.Errorf("build commitment proofs error: %w", err) + } + + s.lock.Lock() + defer s.lock.Unlock() + // update the nextCommittedIndex since a commitment was submitted + s.nextCommittedIndex = commitment.Message.EndID.Uint64() + 1 + // commitment was submitted, so discard what we have in memory, so we can build a new one + s.pendingCommitments = nil + + return nil +} + +// GetStateSyncProof returns the proof for the state sync +func (s *stateSyncManager) GetStateSyncProof(stateSyncID uint64) (types.Proof, error) { + stateSyncProof, err := s.state.getStateSyncProof(stateSyncID) + if err != nil { + return types.Proof{}, fmt.Errorf("cannot get state sync proof for StateSync id %d: %w", stateSyncID, err) + } + + if stateSyncProof == nil { + // check if we might've missed a commitment. if it is so, we didn't build proofs for it while syncing + // if we are all synced up, commitment will be saved through PostBlock, but we wont have proofs, + // so we will build them now and save them to db so that we have proofs for missed commitment + commitment, err := s.state.getCommitmentForStateSync(stateSyncID) + if err != nil { + return types.Proof{}, fmt.Errorf("cannot find commitment for StateSync id %d: %w", stateSyncID, err) + } + + if err := s.buildProofs(commitment.Message); err != nil { + return types.Proof{}, fmt.Errorf("cannot build proofs for commitment for StateSync id %d: %w", stateSyncID, err) + } + + stateSyncProof, err = s.state.getStateSyncProof(stateSyncID) + if err != nil { + return types.Proof{}, fmt.Errorf("cannot get state sync proof for StateSync id %d: %w", stateSyncID, err) + } + } + + return types.Proof{ + Data: stateSyncProof.Proof, + Metadata: map[string]interface{}{ + "StateSync": stateSyncProof.StateSync, + }, + }, nil +} + +// buildProofs builds state sync proofs for the submitted commitment and saves them in boltDb for later execution +func (s *stateSyncManager) buildProofs(commitmentMsg *contractsapi.StateSyncCommitment) error { + from := commitmentMsg.StartID.Uint64() + to := commitmentMsg.EndID.Uint64() + + s.logger.Debug( + "[buildProofs] Building proofs for commitment...", + "fromIndex", from, + "toIndex", to, + ) + + events, err := s.state.getStateSyncEventsForCommitment(from, to) + if err != nil { + return fmt.Errorf("failed to get state sync events for commitment to build proofs. Error: %w", err) + } + + tree, err := createMerkleTree(events) + if err != nil { + return err + } + + stateSyncProofs := make([]*StateSyncProof, len(events)) + + for i, event := range events { + p := tree.GenerateProof(uint64(i), 0) + + stateSyncProofs[i] = &StateSyncProof{ + Proof: p, + StateSync: event, + } + } + + s.logger.Debug( + "[buildProofs] Building proofs for commitment finished.", + "fromIndex", from, + "toIndex", to, + ) + + return s.state.insertStateSyncProofs(stateSyncProofs) +} + +// buildCommitment builds a new commitment, signs it and gossips its vote for it +func (s *stateSyncManager) buildCommitment() error { + s.lock.Lock() + defer s.lock.Unlock() + + epoch := s.epoch + fromIndex := s.nextCommittedIndex + + stateSyncEvents, err := s.state.getStateSyncEventsForCommitment(fromIndex, + fromIndex+s.config.maxCommitmentSize-1) + if err != nil && !errors.Is(err, errNotEnoughStateSyncs) { + return fmt.Errorf("failed to get state sync events for commitment. Error: %w", err) + } + + if len(stateSyncEvents) < minCommitmentSize { + // there is not enough state sync events to build at least the minimum commitment + return nil + } + + if len(s.pendingCommitments) > 0 && + s.pendingCommitments[len(s.pendingCommitments)-1].StartID.Cmp(stateSyncEvents[len(stateSyncEvents)-1].ID) >= 0 { + // already built a commitment of this size which is pending to be submitted + return nil + } + + commitment, err := NewPendingCommitment(epoch, stateSyncEvents) + if err != nil { + return err + } + + hash, err := commitment.Hash() + if err != nil { + return fmt.Errorf("failed to generate hash for commitment. Error: %w", err) + } + + hashBytes := hash.Bytes() + + signature, err := s.config.key.Sign(hashBytes) + if err != nil { + return fmt.Errorf("failed to sign commitment message. Error: %w", err) + } + + sig := &MessageSignature{ + From: s.config.key.String(), + Signature: signature, + } + + if _, err = s.state.insertMessageVote(epoch, hashBytes, sig); err != nil { + return fmt.Errorf( + "failed to insert signature for hash=%v to the state. Error: %w", + hex.EncodeToString(hashBytes), + err, + ) + } + + // gossip message + s.multicast(&TransportMessage{ + Hash: hashBytes, + Signature: signature, + NodeID: s.config.key.String(), + EpochNumber: epoch, + }) + + s.logger.Debug( + "[buildCommitment] Built commitment", + "from", commitment.StartID.Uint64(), + "to", commitment.EndID.Uint64(), + ) + + s.pendingCommitments = append(s.pendingCommitments, commitment) + + return nil +} + +// multicast publishes given message to the rest of the network +func (s *stateSyncManager) multicast(msg interface{}) { + data, err := json.Marshal(msg) + if err != nil { + s.logger.Warn("failed to marshal bridge message", "err", err) + + return + } + + err = s.config.topic.Publish(&polybftProto.TransportMessage{Data: data}) + if err != nil { + s.logger.Warn("failed to gossip bridge message", "err", err) + } +} diff --git a/consensus/polybft/state_sync_manager_test.go b/consensus/polybft/state_sync_manager_test.go new file mode 100644 index 0000000000..af61cac176 --- /dev/null +++ b/consensus/polybft/state_sync_manager_test.go @@ -0,0 +1,490 @@ +package polybft + +import ( + "fmt" + "math/big" + "math/rand" + "os" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/abi" + "github.com/umbracle/ethgo/testutil" + "google.golang.org/protobuf/proto" +) + +func newTestStateSyncManager(t *testing.T, key *testValidator) *stateSyncManager { + t.Helper() + + tmpDir, err := os.MkdirTemp("/tmp", "test-data-dir-state-sync") + + state := newTestState(t) + require.NoError(t, state.insertEpoch(0)) + + topic := &mockTopic{} + + s, err := NewStateSyncManager(hclog.NewNullLogger(), state, + &stateSyncConfig{ + stateSenderAddr: types.Address{}, + jsonrpcAddr: "", + dataDir: tmpDir, + topic: topic, + key: key.Key(), + maxCommitmentSize: maxCommitmentSize, + }) + + require.NoError(t, err) + + t.Cleanup(func() { + os.RemoveAll(tmpDir) + }) + + return s +} + +func TestStateSyncManager_PostEpoch_BuildCommitment(t *testing.T) { + vals := newTestValidators(5) + s := newTestStateSyncManager(t, vals.getValidator("0")) + + // there are no state syncs + require.NoError(t, s.buildCommitment()) + require.Nil(t, s.pendingCommitments) + + stateSyncs10 := generateStateSyncEvents(t, 10, 0) + + // add 5 state syncs starting in index 0, it will generate one smaller commitment + for i := 0; i < 5; i++ { + require.NoError(t, s.state.insertStateSyncEvent(stateSyncs10[i])) + } + + require.NoError(t, s.buildCommitment()) + require.Len(t, s.pendingCommitments, 1) + require.Equal(t, uint64(0), s.pendingCommitments[0].StartID.Uint64()) + require.Equal(t, uint64(4), s.pendingCommitments[0].EndID.Uint64()) + require.Equal(t, uint64(0), s.pendingCommitments[0].Epoch) + + // add the next 5 state syncs, at that point, so that it generates a larger commitment + for i := 5; i < 10; i++ { + require.NoError(t, s.state.insertStateSyncEvent(stateSyncs10[i])) + } + + require.NoError(t, s.buildCommitment()) + require.Len(t, s.pendingCommitments, 2) + require.Equal(t, uint64(0), s.pendingCommitments[1].StartID.Uint64()) + require.Equal(t, uint64(9), s.pendingCommitments[1].EndID.Uint64()) + require.Equal(t, uint64(0), s.pendingCommitments[1].Epoch) + + // the message was sent + require.NotNil(t, s.config.topic.(*mockTopic).consume()) //nolint +} + +func TestStateSyncManager_MessagePool_OldEpoch(t *testing.T) { + vals := newTestValidators(5) + + s := newTestStateSyncManager(t, vals.getValidator("0")) + s.epoch = 1 + + msg := &TransportMessage{ + EpochNumber: 0, + } + err := s.saveVote(msg) + require.NoError(t, err) +} + +type mockMsg struct { + hash []byte + epoch uint64 +} + +func newMockMsg() *mockMsg { + hash := make([]byte, 32) + rand.Read(hash) + + return &mockMsg{hash: hash} +} + +func (m *mockMsg) WithHash(hash []byte) *mockMsg { + m.hash = hash + + return m +} + +func (m *mockMsg) sign(val *testValidator) *TransportMessage { + signature, err := val.mustSign(m.hash).Marshal() + if err != nil { + panic(fmt.Errorf("BUG: %w", err)) + } + + return &TransportMessage{ + Hash: m.hash, + Signature: signature, + NodeID: val.Address().String(), + EpochNumber: m.epoch, + } +} + +func TestStateSyncManager_MessagePool_SenderIsNoValidator(t *testing.T) { + vals := newTestValidators(5) + + s := newTestStateSyncManager(t, vals.getValidator("0")) + s.validatorSet = vals.toValidatorSet() + + badVal := newTestValidator("a", 0) + msg := newMockMsg().sign(badVal) + + err := s.saveVote(msg) + require.Error(t, err) +} + +func TestStateSyncManager_MessagePool_SenderVotes(t *testing.T) { + vals := newTestValidators(5) + + s := newTestStateSyncManager(t, vals.getValidator("0")) + s.validatorSet = vals.toValidatorSet() + + msg := newMockMsg() + val1signed := msg.sign(vals.getValidator("1")) + val2signed := msg.sign(vals.getValidator("2")) + + // vote with validator 1 + require.NoError(t, s.saveVote(val1signed)) + + votes, err := s.state.getMessageVotes(0, msg.hash) + require.NoError(t, err) + require.Len(t, votes, 1) + + // vote with validator 1 again (the votes do not increase) + require.NoError(t, s.saveVote(val1signed)) + votes, _ = s.state.getMessageVotes(0, msg.hash) + require.Len(t, votes, 1) + + // vote with validator 2 + require.NoError(t, s.saveVote(val2signed)) + votes, _ = s.state.getMessageVotes(0, msg.hash) + require.Len(t, votes, 2) +} + +func TestStateSyncManager_BuildCommitment(t *testing.T) { + vals := newTestValidators(5) + + s := newTestStateSyncManager(t, vals.getValidator("0")) + s.validatorSet = vals.toValidatorSet() + + // commitment is empty + commitment, err := s.Commitment() + require.NoError(t, err) + require.Nil(t, commitment) + + tree, err := NewMerkleTree([][]byte{{0x1}}) + require.NoError(t, err) + + s.pendingCommitments = []*PendingCommitment{ + { + MerkleTree: tree, + StateSyncCommitment: &contractsapi.StateSyncCommitment{ + Root: tree.Hash(), + StartID: big.NewInt(0), + EndID: big.NewInt(1), + }, + }, + } + + hash, err := s.pendingCommitments[0].Hash() + require.NoError(t, err) + + msg := newMockMsg().WithHash(hash.Bytes()) + + // validators 0 and 1 vote for the proposal, there is not enough + // voting power for the proposal + require.NoError(t, s.saveVote(msg.sign(vals.getValidator("0")))) + require.NoError(t, s.saveVote(msg.sign(vals.getValidator("1")))) + + commitment, err = s.Commitment() + require.NoError(t, err) // there is no error if quorum is not met, since its a valid case + require.Nil(t, commitment) + + // validator 2 and 3 vote for the proposal, there is enough voting power now + require.NoError(t, s.saveVote(msg.sign(vals.getValidator("2")))) + require.NoError(t, s.saveVote(msg.sign(vals.getValidator("3")))) + + commitment, err = s.Commitment() + require.NoError(t, err) + require.NotNil(t, commitment) +} + +func TestStateSyncerManager_BuildProofs(t *testing.T) { + vals := newTestValidators(5) + + s := newTestStateSyncManager(t, vals.getValidator("0")) + + for _, evnt := range generateStateSyncEvents(t, 20, 0) { + require.NoError(t, s.state.insertStateSyncEvent(evnt)) + } + + require.NoError(t, s.buildCommitment()) + require.Len(t, s.pendingCommitments, 1) + + mockMsg := &CommitmentMessageSigned{ + Message: &contractsapi.StateSyncCommitment{ + StartID: s.pendingCommitments[0].StartID, + EndID: s.pendingCommitments[0].EndID, + }, + } + + txData, err := mockMsg.EncodeAbi() + require.NoError(t, err) + + tx := createStateTransactionWithData(types.Address{}, txData) + + req := &PostBlockRequest{ + FullBlock: &types.FullBlock{ + Block: &types.Block{ + Transactions: []*types.Transaction{tx}, + }, + }, + } + + require.NoError(t, s.PostBlock(req)) + require.Equal(t, mockMsg.Message.EndID.Uint64()+1, s.nextCommittedIndex) + + for i := uint64(0); i < 10; i++ { + proof, err := s.state.getStateSyncProof(i) + require.NoError(t, err) + require.NotNil(t, proof) + } +} + +func TestStateSyncerManager_AddLog_BuildCommitments(t *testing.T) { + vals := newTestValidators(5) + + s := newTestStateSyncManager(t, vals.getValidator("0")) + + // empty log which is not an state sync + s.AddLog(ðgo.Log{}) + stateSyncs, err := s.state.list() + + require.NoError(t, err) + require.Len(t, stateSyncs, 0) + + // log with the state sync topic but incorrect content + s.AddLog(ðgo.Log{Topics: []ethgo.Hash{stateTransferEventABI.ID()}}) + stateSyncs, err = s.state.list() + + require.NoError(t, err) + require.Len(t, stateSyncs, 0) + + // correct event log + data, err := abi.MustNewType("tuple(string a)").Encode([]string{"data"}) + require.NoError(t, err) + + goodLog := ðgo.Log{ + Topics: []ethgo.Hash{ + stateTransferEventABI.ID(), + ethgo.BytesToHash([]byte{0x0}), // state sync index 0 + ethgo.ZeroHash, + ethgo.ZeroHash, + }, + Data: data, + } + + s.AddLog(goodLog) + + stateSyncs, err = s.state.getStateSyncEventsForCommitment(0, 0) + require.NoError(t, err) + require.Len(t, stateSyncs, 1) + require.Len(t, s.pendingCommitments, 0) + + // add one more log to have a minimum commitment + goodLog2 := goodLog.Copy() + goodLog2.Topics[1] = ethgo.BytesToHash([]byte{0x1}) // state sync index 1 + s.AddLog(goodLog2) + + require.Len(t, s.pendingCommitments, 1) + require.Equal(t, uint64(0), s.pendingCommitments[0].StartID.Uint64()) + require.Equal(t, uint64(1), s.pendingCommitments[0].EndID.Uint64()) + + // add two more logs to have larger commitments + goodLog3 := goodLog.Copy() + goodLog3.Topics[1] = ethgo.BytesToHash([]byte{0x2}) // state sync index 2 + s.AddLog(goodLog3) + + goodLog4 := goodLog.Copy() + goodLog4.Topics[1] = ethgo.BytesToHash([]byte{0x3}) // state sync index 3 + s.AddLog(goodLog4) + + require.Len(t, s.pendingCommitments, 3) + require.Equal(t, uint64(0), s.pendingCommitments[2].StartID.Uint64()) + require.Equal(t, uint64(3), s.pendingCommitments[2].EndID.Uint64()) +} + +func TestStateSyncerManager_EventTracker_Sync(t *testing.T) { + t.Parallel() + + vals := newTestValidators(5) + s := newTestStateSyncManager(t, vals.getValidator("0")) + + server := testutil.DeployTestServer(t, nil) + + // TODO: Deploy local artifacts + cc := &testutil.Contract{} + cc.AddCallback(func() string { + return ` + event StateSynced(uint256 indexed id, address indexed sender, address indexed receiver, bytes data); + uint256 indx; + + function emitEvent() public payable { + emit StateSynced(indx, msg.sender, msg.sender, bytes("")); + indx++; + } + ` + }) + + _, addr, err := server.DeployContract(cc) + require.NoError(t, err) + + // prefill with 10 events + for i := 0; i < 10; i++ { + receipt, err := server.TxnTo(addr, "emitEvent") + require.NoError(t, err) + require.Equal(t, uint64(types.ReceiptSuccess), receipt.Status) + } + + s.config.stateSenderAddr = types.Address(addr) + s.config.jsonrpcAddr = server.HTTPAddr() + + require.NoError(t, s.initTracker()) + + time.Sleep(2 * time.Second) + + events, err := s.state.getStateSyncEventsForCommitment(0, 9) + require.NoError(t, err) + require.Len(t, events, 10) +} + +func TestStateSyncManager_Close(t *testing.T) { + t.Parallel() + + mgr := newTestStateSyncManager(t, newTestValidator("A", 100)) + require.NotPanics(t, func() { mgr.Close() }) +} + +func TestStateSyncManager_GetProofs(t *testing.T) { + t.Parallel() + + const ( + stateSyncID = uint64(5) + ) + + state := newTestState(t) + insertTestStateSyncProofs(t, state, maxCommitmentSize) + + stateSyncManager := &stateSyncManager{state: state} + + proof, err := stateSyncManager.GetStateSyncProof(stateSyncID) + require.NoError(t, err) + + stateSync, ok := (proof.Metadata["StateSync"]).(*contractsapi.StateSyncedEvent) + require.True(t, ok) + require.Equal(t, stateSyncID, stateSync.ID.Uint64()) + require.NotEmpty(t, proof.Data) +} + +func TestStateSyncManager_GetProofs_NoProof_NoCommitment(t *testing.T) { + t.Parallel() + + const ( + stateSyncID = uint64(5) + ) + + stateSyncManager := &stateSyncManager{state: newTestState(t)} + + _, err := stateSyncManager.GetStateSyncProof(stateSyncID) + require.ErrorContains(t, err, "cannot find commitment for StateSync id") +} + +func TestStateSyncManager_GetProofs_NoProof_HasCommitment_NoStateSyncs(t *testing.T) { + t.Parallel() + + const ( + stateSyncID = uint64(5) + ) + + state := newTestState(t) + require.NoError(t, state.insertCommitmentMessage(createTestCommitmentMessage(t, 1))) + + stateSyncManager := &stateSyncManager{state: state, logger: hclog.NewNullLogger()} + + _, err := stateSyncManager.GetStateSyncProof(stateSyncID) + require.ErrorContains(t, err, "failed to get state sync events for commitment to build proofs") +} + +func TestStateSyncManager_GetProofs_NoProof_BuildProofs(t *testing.T) { + t.Parallel() + + const ( + stateSyncID = uint64(5) + fromIndex = 1 + ) + + state := newTestState(t) + stateSyncs := generateStateSyncEvents(t, maxCommitmentSize, fromIndex) + + tree, err := createMerkleTree(stateSyncs) + require.NoError(t, err) + + commitment := &CommitmentMessageSigned{ + Message: &contractsapi.StateSyncCommitment{ + StartID: big.NewInt(fromIndex), + EndID: big.NewInt(maxCommitmentSize), + Root: tree.Hash(), + }, + } + + for _, sse := range stateSyncs { + require.NoError(t, state.insertStateSyncEvent(sse)) + } + + require.NoError(t, state.insertCommitmentMessage(commitment)) + + stateSyncManager := &stateSyncManager{state: state, logger: hclog.NewNullLogger()} + + proof, err := stateSyncManager.GetStateSyncProof(stateSyncID) + require.NoError(t, err) + + stateSync, ok := (proof.Metadata["StateSync"]).(*contractsapi.StateSyncedEvent) + require.True(t, ok) + require.Equal(t, stateSyncID, stateSync.ID.Uint64()) + require.NotEmpty(t, proof.Data) + + require.NoError(t, commitment.VerifyStateSyncProof(proof.Data, stateSync)) +} + +type mockTopic struct { + published proto.Message +} + +func (m *mockTopic) consume() proto.Message { + msg := m.published + + if m.published != nil { + m.published = nil + } + + return msg +} + +func (m *mockTopic) Publish(obj proto.Message) error { + m.published = obj + + return nil +} + +func (m *mockTopic) Subscribe(handler func(obj interface{}, from peer.ID)) error { + return nil +} diff --git a/consensus/polybft/state_test.go b/consensus/polybft/state_test.go new file mode 100644 index 0000000000..2cc620c0c9 --- /dev/null +++ b/consensus/polybft/state_test.go @@ -0,0 +1,632 @@ +package polybft + +import ( + "bytes" + "fmt" + "math/big" + "os" + "path" + "sync" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/abi" +) + +func newTestState(t *testing.T) *State { + t.Helper() + + dir := fmt.Sprintf("/tmp/consensus-temp_%v", time.Now().Format(time.RFC3339Nano)) + err := os.Mkdir(dir, 0777) + + if err != nil { + t.Fatal(err) + } + + state, err := newState(path.Join(dir, "my.db"), hclog.NewNullLogger(), make(chan struct{})) + if err != nil { + t.Fatal(err) + } + + t.Cleanup(func() { + if err := os.RemoveAll(dir); err != nil { + t.Fatal(err) + } + }) + + return state +} + +func TestState_InsertEvent(t *testing.T) { + t.Parallel() + + state := newTestState(t) + event1 := &contractsapi.StateSyncedEvent{ + ID: big.NewInt(0), + Sender: types.Address{}, + Receiver: types.Address{}, + Data: []byte{}, + } + + err := state.insertStateSyncEvent(event1) + assert.NoError(t, err) + + events, err := state.list() + assert.NoError(t, err) + assert.Len(t, events, 1) +} + +func TestState_Insert_And_Get_MessageVotes(t *testing.T) { + t.Parallel() + + state := newTestState(t) + epoch := uint64(1) + assert.NoError(t, state.insertEpoch(epoch)) + + hash := []byte{1, 2} + _, err := state.insertMessageVote(1, hash, &MessageSignature{ + From: "NODE_1", + Signature: []byte{1, 2}, + }) + + assert.NoError(t, err) + + votes, err := state.getMessageVotes(epoch, hash) + assert.NoError(t, err) + assert.Equal(t, 1, len(votes)) + assert.Equal(t, "NODE_1", votes[0].From) + assert.True(t, bytes.Equal([]byte{1, 2}, votes[0].Signature)) +} + +func TestState_InsertVoteConcurrent(t *testing.T) { + t.Parallel() + + state := newTestState(t) + epoch := uint64(1) + assert.NoError(t, state.insertEpoch(epoch)) + + hash := []byte{1, 2} + + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + + go func(i int) { + defer wg.Done() + + _, _ = state.insertMessageVote(epoch, hash, &MessageSignature{ + From: fmt.Sprintf("NODE_%d", i), + Signature: []byte{1, 2}, + }) + }(i) + } + + wg.Wait() + + signatures, err := state.getMessageVotes(epoch, hash) + assert.NoError(t, err) + assert.Len(t, signatures, 100) +} + +func TestState_Insert_And_Cleanup(t *testing.T) { + t.Parallel() + + state := newTestState(t) + hash1 := []byte{1, 2} + + for i := uint64(1); i <= 500; i++ { + epoch := i + err := state.insertEpoch(epoch) + + assert.NoError(t, err) + + _, _ = state.insertMessageVote(epoch, hash1, &MessageSignature{ + From: "NODE_1", + Signature: []byte{1, 2}, + }) + } + + // BucketN returns number of all buckets inside root bucket (including nested buckets) + the root itself + // Since we inserted 500 epochs we expect to have 1000 buckets inside epochs root bucket + // (500 buckets for epochs + each epoch has 1 nested bucket for message votes) + assert.Equal(t, 1000, state.epochsDBStats().BucketN-1) + + err := state.cleanEpochsFromDB() + assert.NoError(t, err) + + assert.Equal(t, 0, state.epochsDBStats().BucketN-1) + + // there should be no votes for given epoch since we cleaned the db + votes, _ := state.getMessageVotes(1, hash1) + assert.Nil(t, votes) + + for i := uint64(501); i <= 1000; i++ { + epoch := i + err := state.insertEpoch(epoch) + assert.NoError(t, err) + + _, _ = state.insertMessageVote(epoch, hash1, &MessageSignature{ + From: "NODE_1", + Signature: []byte{1, 2}, + }) + } + + assert.Equal(t, 1000, state.epochsDBStats().BucketN-1) + + votes, _ = state.getMessageVotes(1000, hash1) + assert.Equal(t, 1, len(votes)) +} + +func TestState_insertAndGetValidatorSnapshot(t *testing.T) { + t.Parallel() + + const ( + epoch = uint64(1) + epochEndingBlock = uint64(100) + ) + + state := newTestState(t) + keys, err := bls.CreateRandomBlsKeys(3) + + require.NoError(t, err) + + snapshot := AccountSet{ + &ValidatorMetadata{Address: types.BytesToAddress([]byte{0x18}), BlsKey: keys[0].PublicKey()}, + &ValidatorMetadata{Address: types.BytesToAddress([]byte{0x23}), BlsKey: keys[1].PublicKey()}, + &ValidatorMetadata{Address: types.BytesToAddress([]byte{0x37}), BlsKey: keys[2].PublicKey()}, + } + + assert.NoError(t, state.insertValidatorSnapshot(&validatorSnapshot{epoch, epochEndingBlock, snapshot})) + + snapshotFromDB, err := state.getValidatorSnapshot(epoch) + + assert.NoError(t, err) + assert.Equal(t, snapshot.Len(), snapshotFromDB.Snapshot.Len()) + assert.Equal(t, epoch, snapshotFromDB.Epoch) + assert.Equal(t, epochEndingBlock, snapshotFromDB.EpochEndingBlock) + + for i, v := range snapshot { + assert.Equal(t, v.Address, snapshotFromDB.Snapshot[i].Address) + assert.Equal(t, v.BlsKey, snapshotFromDB.Snapshot[i].BlsKey) + } +} + +func TestState_getLastSnapshot(t *testing.T) { + t.Parallel() + + const ( + lastEpoch = uint64(10) + fixedEpochSize = uint64(10) + numberOfValidators = 3 + ) + + state := newTestState(t) + + for i := uint64(1); i <= lastEpoch; i++ { + keys, err := bls.CreateRandomBlsKeys(numberOfValidators) + + require.NoError(t, err) + + var snapshot AccountSet + for j := 0; j < numberOfValidators; j++ { + snapshot = append(snapshot, &ValidatorMetadata{Address: types.BytesToAddress(generateRandomBytes(t)), BlsKey: keys[j].PublicKey()}) + } + + require.NoError(t, state.insertValidatorSnapshot(&validatorSnapshot{i, i * fixedEpochSize, snapshot})) + } + + snapshotFromDB, err := state.getLastSnapshot() + + assert.NoError(t, err) + assert.Equal(t, numberOfValidators, snapshotFromDB.Snapshot.Len()) + assert.Equal(t, lastEpoch, snapshotFromDB.Epoch) + assert.Equal(t, lastEpoch*fixedEpochSize, snapshotFromDB.EpochEndingBlock) +} + +func TestState_cleanValidatorSnapshotsFromDb(t *testing.T) { + t.Parallel() + + fixedEpochSize := uint64(10) + state := newTestState(t) + keys, err := bls.CreateRandomBlsKeys(3) + require.NoError(t, err) + + snapshot := AccountSet{ + &ValidatorMetadata{Address: types.BytesToAddress([]byte{0x18}), BlsKey: keys[0].PublicKey()}, + &ValidatorMetadata{Address: types.BytesToAddress([]byte{0x23}), BlsKey: keys[1].PublicKey()}, + &ValidatorMetadata{Address: types.BytesToAddress([]byte{0x37}), BlsKey: keys[2].PublicKey()}, + } + + var epoch uint64 + // add a couple of more snapshots above limit just to make sure we reached it + for i := 1; i <= validatorSnapshotLimit+2; i++ { + epoch = uint64(i) + assert.NoError(t, state.insertValidatorSnapshot(&validatorSnapshot{epoch, epoch * fixedEpochSize, snapshot})) + } + + snapshotFromDB, err := state.getValidatorSnapshot(epoch) + + assert.NoError(t, err) + assert.Equal(t, snapshot.Len(), snapshotFromDB.Snapshot.Len()) + assert.Equal(t, epoch, snapshotFromDB.Epoch) + assert.Equal(t, epoch*fixedEpochSize, snapshotFromDB.EpochEndingBlock) + + for i, v := range snapshot { + assert.Equal(t, v.Address, snapshotFromDB.Snapshot[i].Address) + assert.Equal(t, v.BlsKey, snapshotFromDB.Snapshot[i].BlsKey) + } + + assert.NoError(t, state.cleanValidatorSnapshotsFromDB(epoch)) + + // test that last (numberOfSnapshotsToLeaveInDb) of snapshots are left in db after cleanup + validatorSnapshotsBucketStats := state.validatorSnapshotsDBStats() + + assert.Equal(t, numberOfSnapshotsToLeaveInDB, validatorSnapshotsBucketStats.KeyN) + + for i := 0; i < numberOfSnapshotsToLeaveInDB; i++ { + snapshotFromDB, err = state.getValidatorSnapshot(epoch) + assert.NoError(t, err) + assert.NotNil(t, snapshotFromDB) + epoch-- + } +} + +func TestState_getStateSyncEventsForCommitment_NotEnoughEvents(t *testing.T) { + t.Parallel() + + state := newTestState(t) + + for i := 0; i < maxCommitmentSize-2; i++ { + assert.NoError(t, state.insertStateSyncEvent(&contractsapi.StateSyncedEvent{ + ID: big.NewInt(int64(i)), + Data: []byte{1, 2}, + })) + } + + _, err := state.getStateSyncEventsForCommitment(0, maxCommitmentSize-1) + assert.ErrorIs(t, err, errNotEnoughStateSyncs) +} + +func TestState_getStateSyncEventsForCommitment(t *testing.T) { + t.Parallel() + + state := newTestState(t) + + for i := 0; i < maxCommitmentSize; i++ { + assert.NoError(t, state.insertStateSyncEvent(&contractsapi.StateSyncedEvent{ + ID: big.NewInt(int64(i)), + Data: []byte{1, 2}, + })) + } + + t.Run("Return all - forced. Enough events", func(t *testing.T) { + t.Parallel() + + events, err := state.getStateSyncEventsForCommitment(0, maxCommitmentSize-1) + require.NoError(t, err) + require.Equal(t, maxCommitmentSize, len(events)) + }) + + t.Run("Return all - forced. Not enough events", func(t *testing.T) { + t.Parallel() + + _, err := state.getStateSyncEventsForCommitment(0, maxCommitmentSize+1) + require.ErrorIs(t, err, errNotEnoughStateSyncs) + }) + + t.Run("Return all you can. Enough events", func(t *testing.T) { + t.Parallel() + + events, err := state.getStateSyncEventsForCommitment(0, maxCommitmentSize-1) + assert.NoError(t, err) + assert.Equal(t, maxCommitmentSize, len(events)) + }) + + t.Run("Return all you can. Not enough events", func(t *testing.T) { + t.Parallel() + + events, err := state.getStateSyncEventsForCommitment(0, maxCommitmentSize+1) + assert.ErrorIs(t, err, errNotEnoughStateSyncs) + assert.Equal(t, maxCommitmentSize, len(events)) + }) +} + +func TestState_insertCommitmentMessage(t *testing.T) { + t.Parallel() + + commitment := createTestCommitmentMessage(t, 11) + + state := newTestState(t) + assert.NoError(t, state.insertCommitmentMessage(commitment)) + + commitmentFromDB, err := state.getCommitmentMessage(commitment.Message.EndID.Uint64()) + + assert.NoError(t, err) + assert.NotNil(t, commitmentFromDB) + assert.Equal(t, commitment, commitmentFromDB) +} + +func TestState_StateSync_insertAndGetStateSyncProof(t *testing.T) { + t.Parallel() + + state := newTestState(t) + commitment := createTestCommitmentMessage(t, 0) + require.NoError(t, state.insertCommitmentMessage(commitment)) + + insertTestStateSyncProofs(t, state, 10) + + proofFromDB, err := state.getStateSyncProof(1) + + assert.NoError(t, err) + assert.Equal(t, uint64(1), proofFromDB.StateSync.ID.Uint64()) + assert.NotNil(t, proofFromDB.Proof) +} + +func TestState_Insert_And_Get_ExitEvents_PerEpoch(t *testing.T) { + const ( + numOfEpochs = 11 + numOfBlocksPerEpoch = 10 + numOfEventsPerBlock = 11 + ) + + state := newTestState(t) + insertTestExitEvents(t, state, numOfEpochs, numOfBlocksPerEpoch, numOfEventsPerBlock) + + t.Run("Get events for existing epoch", func(t *testing.T) { + events, err := state.getExitEventsByEpoch(1) + + assert.NoError(t, err) + assert.Len(t, events, numOfBlocksPerEpoch*numOfEventsPerBlock) + }) + + t.Run("Get events for non-existing epoch", func(t *testing.T) { + events, err := state.getExitEventsByEpoch(12) + + assert.NoError(t, err) + assert.Len(t, events, 0) + }) +} + +func TestState_Insert_And_Get_ExitEvents_ForProof(t *testing.T) { + const ( + numOfEpochs = 11 + numOfBlocksPerEpoch = 10 + numOfEventsPerBlock = 10 + ) + + state := newTestState(t) + insertTestExitEvents(t, state, numOfEpochs, numOfBlocksPerEpoch, numOfEventsPerBlock) + + var cases = []struct { + epoch uint64 + checkpointBlockNumber uint64 + expectedNumberOfEvents int + }{ + {1, 1, 10}, + {1, 2, 20}, + {1, 8, 80}, + {2, 12, 20}, + {2, 14, 40}, + {3, 26, 60}, + {4, 38, 80}, + {11, 105, 50}, + } + + for _, c := range cases { + events, err := state.getExitEventsForProof(c.epoch, c.checkpointBlockNumber) + + assert.NoError(t, err) + assert.Len(t, events, c.expectedNumberOfEvents) + } +} + +func TestState_Insert_And_Get_ExitEvents_ForProof_NoEvents(t *testing.T) { + state := newTestState(t) + insertTestExitEvents(t, state, 1, 10, 1) + + events, err := state.getExitEventsForProof(2, 11) + + assert.NoError(t, err) + assert.Nil(t, events) +} + +func TestState_decodeExitEvent(t *testing.T) { + t.Parallel() + + const ( + exitID = 1 + epoch = 1 + blockNumber = 10 + ) + + state := newTestState(t) + + topics := make([]ethgo.Hash, 4) + topics[0] = exitEventABI.ID() + topics[1] = ethgo.BytesToHash([]byte{exitID}) + topics[2] = ethgo.BytesToHash(ethgo.HexToAddress("0x1111").Bytes()) + topics[3] = ethgo.BytesToHash(ethgo.HexToAddress("0x2222").Bytes()) + personType := abi.MustNewType("tuple(string firstName, string lastName)") + encodedData, err := personType.Encode(map[string]string{"firstName": "John", "lastName": "Doe"}) + require.NoError(t, err) + + log := ðgo.Log{ + Address: ethgo.ZeroAddress, + Topics: topics, + Data: encodedData, + } + + event, err := decodeExitEvent(log, epoch, blockNumber) + require.NoError(t, err) + require.Equal(t, uint64(exitID), event.ID) + require.Equal(t, uint64(epoch), event.EpochNumber) + require.Equal(t, uint64(blockNumber), event.BlockNumber) + + require.NoError(t, state.insertExitEvent(event)) +} + +func TestState_decodeExitEvent_NotAnExitEvent(t *testing.T) { + t.Parallel() + + topics := make([]ethgo.Hash, 4) + topics[0] = stateTransferEventABI.ID() + + log := ðgo.Log{ + Address: ethgo.ZeroAddress, + Topics: topics, + } + + event, err := decodeExitEvent(log, 1, 1) + require.NoError(t, err) + require.Nil(t, event) +} + +func TestState_getProposerSnapshot_writeProposerSnapshot(t *testing.T) { + t.Parallel() + + const ( + height = uint64(100) + round = uint64(5) + ) + + state := newTestState(t) + + snap, err := state.getProposerSnapshot() + require.NoError(t, err) + require.Nil(t, snap) + + newSnapshot := &ProposerSnapshot{Height: height, Round: round} + require.NoError(t, state.writeProposerSnapshot(newSnapshot)) + + snap, err = state.getProposerSnapshot() + require.NoError(t, err) + require.Equal(t, newSnapshot, snap) +} + +func insertTestExitEvents(t *testing.T, state *State, + numOfEpochs, numOfBlocksPerEpoch, numOfEventsPerBlock int) { + t.Helper() + + index, block := uint64(1), uint64(1) + + for i := uint64(1); i <= uint64(numOfEpochs); i++ { + for j := 1; j <= numOfBlocksPerEpoch; j++ { + for k := 1; k <= numOfEventsPerBlock; k++ { + event := &ExitEvent{index, ethgo.HexToAddress("0x101"), ethgo.HexToAddress("0x102"), []byte{11, 22}, i, block} + assert.NoError(t, state.insertExitEvent(event)) + + index++ + } + block++ + } + } +} + +func TestState_getCommitmentForStateSync(t *testing.T) { + const ( + numOfCommitments = 10 + ) + + state := newTestState(t) + + insertTestCommitments(t, state, numOfCommitments) + + var cases = []struct { + stateSyncID uint64 + hasCommitment bool + }{ + {1, true}, + {10, true}, + {11, true}, + {7, true}, + {999, false}, + {121, false}, + {99, true}, + {101, true}, + {111, false}, + {75, true}, + {5, true}, + {102, true}, + {211, false}, + {21, true}, + {30, true}, + {81, true}, + {90, true}, + } + + for _, c := range cases { + commitment, err := state.getCommitmentForStateSync(c.stateSyncID) + + if c.hasCommitment { + require.NoError(t, err, fmt.Sprintf("state sync %v", c.stateSyncID)) + require.Equal(t, c.hasCommitment, commitment.ContainsStateSync(c.stateSyncID)) + } else { + require.ErrorIs(t, errNoCommitmentForStateSync, err) + } + } +} + +func insertTestCommitments(t *testing.T, state *State, numberOfCommitments uint64) { + t.Helper() + + for i := uint64(0); i <= numberOfCommitments; i++ { + commitment := createTestCommitmentMessage(t, i*maxCommitmentSize+1) + require.NoError(t, state.insertCommitmentMessage(commitment)) + } +} + +func insertTestStateSyncProofs(t *testing.T, state *State, numberOfProofs int64) { + t.Helper() + + ssProofs := make([]*StateSyncProof, numberOfProofs) + + for i := int64(0); i < numberOfProofs; i++ { + proofs := &StateSyncProof{ + Proof: []types.Hash{types.BytesToHash(generateRandomBytes(t))}, + StateSync: createTestStateSync(i), + } + ssProofs[i] = proofs + } + + require.NoError(t, state.insertStateSyncProofs(ssProofs)) +} + +func createTestStateSync(index int64) *contractsapi.StateSyncedEvent { + return &contractsapi.StateSyncedEvent{ + ID: big.NewInt(index), + Sender: types.ZeroAddress, + Receiver: types.ZeroAddress, + Data: []byte{0, 1}, + } +} + +func createTestCommitmentMessage(t *testing.T, fromIndex uint64) *CommitmentMessageSigned { + t.Helper() + + tree, err := NewMerkleTree([][]byte{ + {0, 1}, + {2, 3}, + {4, 5}, + }) + + require.NoError(t, err) + + msg := &contractsapi.StateSyncCommitment{ + Root: tree.Hash(), + StartID: big.NewInt(int64(fromIndex)), + EndID: big.NewInt(int64(fromIndex + maxCommitmentSize - 1)), + } + + return &CommitmentMessageSigned{ + Message: msg, + AggSignature: Signature{}, + } +} diff --git a/consensus/polybft/state_transaction.go b/consensus/polybft/state_transaction.go new file mode 100644 index 0000000000..68124ed14c --- /dev/null +++ b/consensus/polybft/state_transaction.go @@ -0,0 +1,218 @@ +package polybft + +import ( + "bytes" + "errors" + "fmt" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/crypto" + "github.com/0xPolygon/polygon-edge/state/runtime/precompiled" + "github.com/0xPolygon/polygon-edge/types" +) + +const ( + abiMethodIDLength = 4 + stTypeBridgeCommitment = "commitment" + stTypeEndEpoch = "end-epoch" +) + +// PendingCommitment holds merkle trie of bridge transactions accompanied by epoch number +type PendingCommitment struct { + *contractsapi.StateSyncCommitment + MerkleTree *MerkleTree + Epoch uint64 +} + +// NewPendingCommitment creates a new commitment object +func NewPendingCommitment(epoch uint64, stateSyncEvents []*contractsapi.StateSyncedEvent) (*PendingCommitment, error) { + tree, err := createMerkleTree(stateSyncEvents) + if err != nil { + return nil, err + } + + return &PendingCommitment{ + MerkleTree: tree, + Epoch: epoch, + StateSyncCommitment: &contractsapi.StateSyncCommitment{ + StartID: stateSyncEvents[0].ID, + EndID: stateSyncEvents[len(stateSyncEvents)-1].ID, + Root: tree.Hash(), + }, + }, nil +} + +// Hash calculates hash value for commitment object. +func (cm *PendingCommitment) Hash() (types.Hash, error) { + data, err := cm.StateSyncCommitment.EncodeAbi() + if err != nil { + return types.Hash{}, err + } + + return crypto.Keccak256Hash(data), nil +} + +var _ contractsapi.StateTransactionInput = &CommitmentMessageSigned{} + +// CommitmentMessageSigned encapsulates commitment message with aggregated signatures +type CommitmentMessageSigned struct { + Message *contractsapi.StateSyncCommitment + AggSignature Signature + PublicKeys [][]byte +} + +// Hash calculates hash value for commitment object. +func (cm *CommitmentMessageSigned) Hash() (types.Hash, error) { + data, err := cm.Message.EncodeAbi() + if err != nil { + return types.Hash{}, err + } + + return crypto.Keccak256Hash(data), nil +} + +// VerifyStateSyncProof validates given state sync proof +// against merkle tree root hash contained in the CommitmentMessage +func (cm *CommitmentMessageSigned) VerifyStateSyncProof(proof []types.Hash, + stateSync *contractsapi.StateSyncedEvent) error { + if stateSync == nil { + return errors.New("no state sync event") + } + + hash, err := stateSync.EncodeAbi() + if err != nil { + return err + } + + return VerifyProof(stateSync.ID.Uint64()-cm.Message.StartID.Uint64(), + hash, proof, cm.Message.Root) +} + +// ContainsStateSync checks if commitment contains given state sync event +func (cm *CommitmentMessageSigned) ContainsStateSync(stateSyncID uint64) bool { + return cm.Message.StartID.Uint64() <= stateSyncID && cm.Message.EndID.Uint64() >= stateSyncID +} + +// EncodeAbi contains logic for encoding arbitrary data into ABI format +func (cm *CommitmentMessageSigned) EncodeAbi() ([]byte, error) { + blsVerificationPart, err := precompiled.BlsVerificationABIType.Encode( + [2]interface{}{cm.PublicKeys, cm.AggSignature.Bitmap}) + if err != nil { + return nil, err + } + + commit := &contractsapi.CommitFunction{ + Commitment: cm.Message, + Signature: cm.AggSignature.AggregatedSignature, + Bitmap: blsVerificationPart, + } + + return commit.EncodeAbi() +} + +// DecodeAbi contains logic for decoding given ABI data +func (cm *CommitmentMessageSigned) DecodeAbi(txData []byte) error { + if len(txData) < abiMethodIDLength { + return fmt.Errorf("invalid commitment data, len = %d", len(txData)) + } + + commit := contractsapi.CommitFunction{} + + err := commit.DecodeAbi(txData) + if err != nil { + return err + } + + decoded, err := precompiled.BlsVerificationABIType.Decode(commit.Bitmap) + if err != nil { + return err + } + + blsMap, isOk := decoded.(map[string]interface{}) + if !isOk { + return fmt.Errorf("invalid commitment data. Bls verification part not in correct format") + } + + publicKeys, isOk := blsMap["0"].([][]byte) + if !isOk { + return fmt.Errorf("invalid commitment data. Could not find public keys part") + } + + bitmap, isOk := blsMap["1"].([]byte) + if !isOk { + return fmt.Errorf("invalid commitment data. Could not find bitmap part") + } + + *cm = CommitmentMessageSigned{ + Message: commit.Commitment, + AggSignature: Signature{ + AggregatedSignature: commit.Signature, + Bitmap: bitmap, + }, + PublicKeys: publicKeys, + } + + return nil +} + +func decodeStateTransaction(txData []byte) (contractsapi.StateTransactionInput, error) { + if len(txData) < abiMethodIDLength { + return nil, fmt.Errorf("state transactions have input") + } + + sig := txData[:abiMethodIDLength] + + var obj contractsapi.StateTransactionInput + + if bytes.Equal(sig, contractsapi.StateReceiver.Abi.Methods["commit"].ID()) { + // bridge commitment + obj = &CommitmentMessageSigned{} + } else if bytes.Equal(sig, contractsapi.ChildValidatorSet.Abi.Methods["commitEpoch"].ID()) { + // uptime + obj = &contractsapi.CommitEpochFunction{} + } else { + return nil, fmt.Errorf("unknown state transaction") + } + + if err := obj.DecodeAbi(txData); err != nil { + return nil, err + } + + return obj, nil +} + +func getCommitmentMessageSignedTx(txs []*types.Transaction) (*CommitmentMessageSigned, error) { + for _, tx := range txs { + // skip non state CommitmentMessageSigned transactions + if tx.Type != types.StateTx || + len(tx.Input) < abiMethodIDLength || + !bytes.Equal(tx.Input[:abiMethodIDLength], contractsapi.StateReceiver.Abi.Methods["commit"].ID()) { + continue + } + + obj := &CommitmentMessageSigned{} + + if err := obj.DecodeAbi(tx.Input); err != nil { + return nil, fmt.Errorf("get commitment message signed tx error: %w", err) + } + + return obj, nil + } + + return nil, nil +} + +func createMerkleTree(stateSyncEvents []*contractsapi.StateSyncedEvent) (*MerkleTree, error) { + ssh := make([][]byte, len(stateSyncEvents)) + + for i, sse := range stateSyncEvents { + data, err := sse.EncodeAbi() + if err != nil { + return nil, err + } + + ssh[i] = data + } + + return NewMerkleTree(ssh) +} diff --git a/consensus/polybft/state_transaction_test.go b/consensus/polybft/state_transaction_test.go new file mode 100644 index 0000000000..03bf0c66f9 --- /dev/null +++ b/consensus/polybft/state_transaction_test.go @@ -0,0 +1,224 @@ +package polybft + +import ( + "encoding/hex" + "math/big" + "reflect" + "testing" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo/abi" +) + +func newTestCommitmentSigned(root types.Hash, startID, endID int64) *CommitmentMessageSigned { + return &CommitmentMessageSigned{ + Message: &contractsapi.StateSyncCommitment{ + StartID: big.NewInt(startID), + EndID: big.NewInt(endID), + Root: root, + }, + AggSignature: Signature{}, + PublicKeys: [][]byte{}, + } +} + +func TestCommitmentMessage_Hash(t *testing.T) { + t.Parallel() + + const ( + eventsCount = 10 + ) + + stateSyncEvents := generateStateSyncEvents(t, eventsCount, 0) + + trie1, err := createMerkleTree(stateSyncEvents) + require.NoError(t, err) + + trie2, err := createMerkleTree(stateSyncEvents[0 : len(stateSyncEvents)-1]) + require.NoError(t, err) + + commitmentMessage1 := newTestCommitmentSigned(trie1.Hash(), 2, 8) + commitmentMessage2 := newTestCommitmentSigned(trie1.Hash(), 2, 8) + commitmentMessage3 := newTestCommitmentSigned(trie1.Hash(), 6, 10) + commitmentMessage4 := newTestCommitmentSigned(trie2.Hash(), 2, 8) + + hash1, err := commitmentMessage1.Hash() + require.NoError(t, err) + hash2, err := commitmentMessage2.Hash() + require.NoError(t, err) + hash3, err := commitmentMessage3.Hash() + require.NoError(t, err) + hash4, err := commitmentMessage4.Hash() + require.NoError(t, err) + + require.Equal(t, hash1, hash2) + require.NotEqual(t, hash1, hash3) + require.NotEqual(t, hash1, hash4) + require.NotEqual(t, hash3, hash4) +} + +func TestCommitmentMessage_ToRegisterCommitmentInputData(t *testing.T) { + t.Parallel() + + const epoch, eventsCount = uint64(100), 11 + pendingCommitment, _, _ := buildCommitmentAndStateSyncs(t, eventsCount, epoch, uint64(2)) + expectedSignedCommitmentMsg := &CommitmentMessageSigned{ + Message: pendingCommitment.StateSyncCommitment, + AggSignature: Signature{ + Bitmap: []byte{5, 1}, + AggregatedSignature: []byte{1, 1}, + }, + PublicKeys: [][]byte{{0, 1}, {2, 3}, {4, 5}}, + } + inputData, err := expectedSignedCommitmentMsg.EncodeAbi() + require.NoError(t, err) + require.NotEmpty(t, inputData) + + var actualSignedCommitmentMsg CommitmentMessageSigned + + require.NoError(t, actualSignedCommitmentMsg.DecodeAbi(inputData)) + require.NoError(t, err) + require.Equal(t, *expectedSignedCommitmentMsg.Message, *actualSignedCommitmentMsg.Message) + require.Equal(t, expectedSignedCommitmentMsg.AggSignature, actualSignedCommitmentMsg.AggSignature) +} + +func TestCommitmentMessage_VerifyProof(t *testing.T) { + t.Parallel() + + const epoch, eventsCount = uint64(100), 11 + commitment, commitmentSigned, stateSyncs := buildCommitmentAndStateSyncs(t, eventsCount, epoch, 0) + require.Equal(t, uint64(10), commitment.EndID.Sub(commitment.EndID, commitment.StartID).Uint64()) + + for i, stateSync := range stateSyncs { + proof := commitment.MerkleTree.GenerateProof(uint64(i), 0) + execute := &contractsapi.ExecuteFunction{ + Proof: proof, + Obj: (*contractsapi.StateSync)(stateSync), + } + + inputData, err := execute.EncodeAbi() + require.NoError(t, err) + + executionStateSync := &contractsapi.ExecuteFunction{} + require.NoError(t, executionStateSync.DecodeAbi(inputData)) + require.Equal(t, stateSync.ID.Uint64(), executionStateSync.Obj.ID.Uint64()) + require.Equal(t, stateSync.Sender, executionStateSync.Obj.Sender) + require.Equal(t, stateSync.Receiver, executionStateSync.Obj.Receiver) + require.Equal(t, stateSync.Data, executionStateSync.Obj.Data) + require.Equal(t, proof, executionStateSync.Proof) + + err = commitmentSigned.VerifyStateSyncProof(executionStateSync.Proof, + (*contractsapi.StateSyncedEvent)(executionStateSync.Obj)) + require.NoError(t, err) + } +} + +func TestCommitmentMessage_VerifyProof_NoStateSyncsInCommitment(t *testing.T) { + t.Parallel() + + commitment := &CommitmentMessageSigned{Message: &contractsapi.StateSyncCommitment{StartID: big.NewInt(1), EndID: big.NewInt(10)}} + err := commitment.VerifyStateSyncProof([]types.Hash{}, nil) + assert.ErrorContains(t, err, "no state sync event") +} + +func TestCommitmentMessage_VerifyProof_StateSyncHashNotEqualToProof(t *testing.T) { + t.Parallel() + + const ( + fromIndex = 0 + toIndex = 4 + ) + + stateSyncs := generateStateSyncEvents(t, 5, 0) + tree, err := createMerkleTree(stateSyncs) + require.NoError(t, err) + + proof := tree.GenerateProof(0, 0) + + commitment := &CommitmentMessageSigned{ + Message: &contractsapi.StateSyncCommitment{ + StartID: big.NewInt(fromIndex), + EndID: big.NewInt(toIndex), + Root: tree.Hash(), + }, + } + + assert.ErrorContains(t, commitment.VerifyStateSyncProof(proof, stateSyncs[4]), "not a member of merkle tree") +} + +func buildCommitmentAndStateSyncs(t *testing.T, stateSyncsCount int, + epoch, startIdx uint64) (*PendingCommitment, *CommitmentMessageSigned, []*contractsapi.StateSyncedEvent) { + t.Helper() + + stateSyncEvents := generateStateSyncEvents(t, stateSyncsCount, startIdx) + commitment, err := NewPendingCommitment(epoch, stateSyncEvents) + require.NoError(t, err) + + commitmentSigned := &CommitmentMessageSigned{ + Message: commitment.StateSyncCommitment, + AggSignature: Signature{ + AggregatedSignature: []byte{}, + Bitmap: []byte{}, + }, + PublicKeys: [][]byte{}, + } + + return commitment, commitmentSigned, stateSyncEvents +} + +func TestStateTransaction_Signature(t *testing.T) { + t.Parallel() + + cases := []struct { + m *abi.Method + sig string + }{ + { + contractsapi.ChildValidatorSet.Abi.GetMethod("commitEpoch"), + "410899c9", + }, + } + for _, c := range cases { + sig := hex.EncodeToString(c.m.ID()) + require.Equal(t, c.sig, sig) + } +} + +func TestStateTransaction_Encoding(t *testing.T) { + t.Parallel() + + cases := []contractsapi.StateTransactionInput{ + &contractsapi.CommitEpochFunction{ + ID: big.NewInt(1), + Epoch: &contractsapi.Epoch{ + StartBlock: big.NewInt(1), + EndBlock: big.NewInt(10), + EpochRoot: types.Hash{}, + }, + Uptime: &contractsapi.Uptime{ + EpochID: big.NewInt(1), + TotalBlocks: big.NewInt(10), + UptimeData: []*contractsapi.UptimeData{}, + }, + }, + } + + for _, c := range cases { + res, err := c.EncodeAbi() + + require.NoError(t, err) + + // use reflection to create another type and decode + val := reflect.New(reflect.TypeOf(c).Elem()).Interface() + obj, ok := val.(contractsapi.StateTransactionInput) + assert.True(t, ok) + + err = obj.DecodeAbi(res) + require.NoError(t, err) + + require.Equal(t, obj, c) + } +} diff --git a/consensus/polybft/statesyncrelayer/state_sync_relayer.go b/consensus/polybft/statesyncrelayer/state_sync_relayer.go new file mode 100644 index 0000000000..fd70b53675 --- /dev/null +++ b/consensus/polybft/statesyncrelayer/state_sync_relayer.go @@ -0,0 +1,225 @@ +package statesyncrelayer + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math/big" + "net" + "path" + "strings" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/tracker" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" + + hcf "github.com/hashicorp/go-hclog" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/jsonrpc" +) + +var commitEvent = contractsapi.StateReceiver.Abi.Events["NewCommitment"] + +type StateSyncRelayer struct { + dataDir string + rpcEndpoint string + stateReceiverAddr ethgo.Address + logger hcf.Logger + client *jsonrpc.Client + txRelayer txrelayer.TxRelayer + key ethgo.Key + closeCh chan struct{} +} + +func sanitizeRPCEndpoint(rpcEndpoint string) string { + if rpcEndpoint == "" || strings.Contains(rpcEndpoint, "0.0.0.0") { + _, port, err := net.SplitHostPort(rpcEndpoint) + if err == nil { + rpcEndpoint = fmt.Sprintf("http://%s:%s", "127.0.0.1", port) + } else { + rpcEndpoint = "http://127.0.0.1:8545" + } + } + + return rpcEndpoint +} + +func NewRelayer( + dataDir string, + rpcEndpoint string, + stateReceiverAddr ethgo.Address, + logger hcf.Logger, + key ethgo.Key, +) *StateSyncRelayer { + endpoint := sanitizeRPCEndpoint(rpcEndpoint) + + // create the JSON RPC client + client, err := jsonrpc.NewClient(endpoint) + if err != nil { + logger.Error("Failed to create the JSON RPC client", "err", err) + + return nil + } + + txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithClient(client)) + if err != nil { + logger.Error("Failed to create the tx relayer", "err", err) + } + + return &StateSyncRelayer{ + dataDir: dataDir, + rpcEndpoint: endpoint, + stateReceiverAddr: stateReceiverAddr, + logger: logger, + client: client, + txRelayer: txRelayer, + key: key, + closeCh: make(chan struct{}), + } +} + +func (r *StateSyncRelayer) Start() error { + et := tracker.NewEventTracker( + path.Join(r.dataDir, "/relayer.db"), + r.rpcEndpoint, + r.stateReceiverAddr, + r, + r.logger, + ) + + ctx, cancelFn := context.WithCancel(context.Background()) + + go func() { + <-r.closeCh + cancelFn() + }() + + return et.Start(ctx) +} + +// Stop function is used to tear down all the allocated resources +func (r *StateSyncRelayer) Stop() { + close(r.closeCh) +} + +func (r *StateSyncRelayer) AddLog(log *ethgo.Log) { + r.logger.Info("Received a log", "log", log) + + if commitEvent.Match(log) { + vals, err := commitEvent.ParseLog(log) + if err != nil { + r.logger.Error("Failed to parse log", "err", err) + + return + } + + var ( + startID, endID *big.Int + ok bool + ) + + if startID, ok = vals["startId"].(*big.Int); !ok { + r.logger.Error("Failed to parse startId") + + return + } + + if endID, ok = vals["endId"].(*big.Int); !ok { + r.logger.Error("Failed to parse endId") + + return + } + + r.logger.Info("Commit", "Block", log.BlockNumber, "StartID", startID, "EndID", endID) + + for i := startID.Int64(); i <= endID.Int64(); i++ { + // query the state sync proof + stateSyncProof, err := r.queryStateSyncProof(fmt.Sprintf("0x%x", int(i))) + if err != nil { + r.logger.Error("Failed to query state sync proof", "err", err) + + continue + } + + if err := r.executeStateSync(stateSyncProof); err != nil { + r.logger.Error("Failed to execute state sync", "err", err) + + continue + } + + r.logger.Info("State sync executed", "stateSyncID", i) + } + } +} + +// queryStateSyncProof queries the state sync proof +func (r *StateSyncRelayer) queryStateSyncProof(stateSyncID string) (*types.Proof, error) { + // retrieve state sync proof + var stateSyncProof types.Proof + + err := r.client.Call("bridge_getStateSyncProof", &stateSyncProof, stateSyncID) + if err != nil { + return nil, err + } + + r.logger.Debug(fmt.Sprintf("state sync proof: %v", stateSyncProof)) + + return &stateSyncProof, nil +} + +// executeStateSync executes the state sync +func (r *StateSyncRelayer) executeStateSync(proof *types.Proof) error { + sseMap, ok := proof.Metadata["StateSync"].(map[string]interface{}) + if !ok { + return errors.New("could not get state sync event from proof") + } + + var sse *contractsapi.StateSync + + // since state sync event is a map in the jsonrpc response, + // to not have custom logic of converting the map to state sync event + // json encoding is used, since it manages to successfully unmarshal the + // event from the marshaled map + raw, err := json.Marshal(sseMap) + if err != nil { + return fmt.Errorf("marshal the state sync map from to byte array failed. Error: %w", err) + } + + err = json.Unmarshal(raw, &sse) + if err != nil { + return fmt.Errorf("unmarshal of state sync from map failed. Error: %w", err) + } + + execute := &contractsapi.ExecuteFunction{ + Proof: proof.Data, + Obj: sse, + } + + input, err := execute.EncodeAbi() + if err != nil { + return err + } + + // execute the state sync + txn := ðgo.Transaction{ + From: r.key.Address(), + To: (*ethgo.Address)(&contracts.StateReceiverContract), + GasPrice: 0, + Gas: types.StateTransactionGasLimit, + Input: input, + } + + receipt, err := r.txRelayer.SendTransaction(txn, r.key) + if err != nil { + return fmt.Errorf("failed to send state sync transaction: %w", err) + } + + if receipt.Status == uint64(types.ReceiptFailed) { + return fmt.Errorf("state sync execution failed: %d", execute.Obj.ID) + } + + return nil +} diff --git a/consensus/polybft/statesyncrelayer/state_sync_relayer_test.go b/consensus/polybft/statesyncrelayer/state_sync_relayer_test.go new file mode 100644 index 0000000000..d0775addc9 --- /dev/null +++ b/consensus/polybft/statesyncrelayer/state_sync_relayer_test.go @@ -0,0 +1,121 @@ +package statesyncrelayer + +import ( + "math/big" + "testing" + + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/wallet" +) + +type txRelayerMock struct { + mock.Mock +} + +func (t *txRelayerMock) Call(from ethgo.Address, to ethgo.Address, input []byte) (string, error) { + args := t.Called(from, to, input) + + return args.String(0), args.Error(1) +} + +func (t *txRelayerMock) SendTransaction(txn *ethgo.Transaction, key ethgo.Key) (*ethgo.Receipt, error) { + args := t.Called(txn, key) + + return args.Get(0).(*ethgo.Receipt), args.Error(1) //nolint:forcetypeassert +} + +func (t *txRelayerMock) SendTransactionLocal(txn *ethgo.Transaction) (*ethgo.Receipt, error) { + args := t.Called(txn) + + return nil, args.Error(1) +} + +func Test_executeStateSync(t *testing.T) { + t.Parallel() + + txRelayer := &txRelayerMock{} + key, _ := wallet.GenerateKey() + + r := &StateSyncRelayer{ + txRelayer: txRelayer, + key: key, + } + + txRelayer.On("SendTransaction", mock.Anything, mock.Anything). + Return(ðgo.Receipt{Status: uint64(types.ReceiptSuccess)}, nil).Once() + + proof := &types.Proof{ + Data: []types.Hash{}, + Metadata: map[string]interface{}{ + "StateSync": map[string]interface{}{ + "ID": big.NewInt(1), + "Sender": types.ZeroAddress, + "Receiver": types.ZeroAddress, + "Data": []byte{}, + }, + }, + } + + require.NoError(t, r.executeStateSync(proof)) + + txRelayer.AssertExpectations(t) +} + +// Test sanitizeRPCEndpoint +func Test_sanitizeRPCEndpoint(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + endpoint string + want string + }{ + { + "url with port", + "http://localhost:10001", + "http://localhost:10001", + }, + { + "all interfaces with port without schema", + "0.0.0.0:10001", + "http://127.0.0.1:10001", + }, + { + "url without port", + "http://127.0.0.1", + "http://127.0.0.1", + }, + { + "empty endpoint", + "", + "http://127.0.0.1:8545", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + if got := sanitizeRPCEndpoint(tt.endpoint); got != tt.want { + t.Errorf("sanitizeRPCEndpoint() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStateSyncRelayer_Stop(t *testing.T) { + t.Parallel() + + key, err := wallet.GenerateKey() + require.NoError(t, err) + + r := NewRelayer("test-chain-1", "http://127.0.0.1:8545", ethgo.Address(contracts.StateReceiverContract), hclog.NewNullLogger(), key) + + require.NotPanics(t, func() { r.Stop() }) +} diff --git a/consensus/polybft/system_state.go b/consensus/polybft/system_state.go new file mode 100644 index 0000000000..4a5f8efa42 --- /dev/null +++ b/consensus/polybft/system_state.go @@ -0,0 +1,172 @@ +package polybft + +import ( + "bytes" + "encoding/json" + "fmt" + "math/big" + "sort" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/contract" +) + +// ValidatorInfo is data transfer object which holds validator information, +// provided by smart contract +type ValidatorInfo struct { + Address ethgo.Address + Stake *big.Int + TotalStake *big.Int + Commission *big.Int + WithdrawableRewards *big.Int + Active bool +} + +// SystemState is an interface to interact with the consensus system contracts in the chain +type SystemState interface { + // GetValidatorSet retrieves current validator set from the smart contract + GetValidatorSet() (AccountSet, error) + // GetEpoch retrieves current epoch number from the smart contract + GetEpoch() (uint64, error) + // GetNextCommittedIndex retrieves next committed bridge state sync index + GetNextCommittedIndex() (uint64, error) +} + +var _ SystemState = &SystemStateImpl{} + +// SystemStateImpl is implementation of SystemState interface +type SystemStateImpl struct { + validatorContract *contract.Contract + sidechainBridgeContract *contract.Contract +} + +// NewSystemState initializes new instance of systemState which abstracts smart contracts functions +func NewSystemState(config *PolyBFTConfig, provider contract.Provider) *SystemStateImpl { + s := &SystemStateImpl{} + s.validatorContract = contract.NewContract( + ethgo.Address(config.ValidatorSetAddr), + contractsapi.ChildValidatorSet.Abi, contract.WithProvider(provider), + ) + s.sidechainBridgeContract = contract.NewContract( + ethgo.Address(config.StateReceiverAddr), + contractsapi.StateReceiver.Abi, + contract.WithProvider(provider), + ) + + return s +} + +// GetValidatorSet retrieves current validator set from the smart contract +func (s *SystemStateImpl) GetValidatorSet() (AccountSet, error) { + output, err := s.validatorContract.Call("getCurrentValidatorSet", ethgo.Latest) + if err != nil { + return nil, err + } + + res := AccountSet{} + + addresses, isOk := output["0"].([]ethgo.Address) + if !isOk { + return nil, fmt.Errorf("failed to decode addresses of the current validator set") + } + + queryValidator := func(addr ethgo.Address) (*ValidatorMetadata, error) { + output, err := s.validatorContract.Call("getValidator", ethgo.Latest, addr) + if err != nil { + return nil, fmt.Errorf("failed to call getValidator function: %w", err) + } + + output, isOk = output["0"].(map[string]interface{}) + if !isOk { + return nil, fmt.Errorf("failed to decode validator data") + } + + pubKey, err := bls.UnmarshalPublicKeyFromBigInt(output["blsKey"].([4]*big.Int)) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal BLS public key: %w", err) + } + + totalStake, ok := output["totalStake"].(*big.Int) + if !ok { + return nil, fmt.Errorf("failed to decode total stake") + } + + val := &ValidatorMetadata{ + Address: types.Address(addr), + BlsKey: pubKey, + VotingPower: new(big.Int).Set(totalStake), + } + + return val, nil + } + + for _, index := range addresses { + val, err := queryValidator(index) + if err != nil { + return nil, err + } + + res = append(res, val) + } + + // It is important to keep validator ordered by addresses, because of internal storage on SC + // which changes original ordering of sent validators + sort.Slice(res, func(i, j int) bool { + return bytes.Compare(res[i].Address[:], res[j].Address[:]) < 0 + }) + + return res, nil +} + +// GetEpoch retrieves current epoch number from the smart contract +func (s *SystemStateImpl) GetEpoch() (uint64, error) { + rawResult, err := s.validatorContract.Call("currentEpochId", ethgo.Latest) + if err != nil { + return 0, err + } + + epochNumber, isOk := rawResult["0"].(*big.Int) + if !isOk { + return 0, fmt.Errorf("failed to decode epoch") + } + + return epochNumber.Uint64(), nil +} + +// GetNextCommittedIndex retrieves next committed bridge state sync index +func (s *SystemStateImpl) GetNextCommittedIndex() (uint64, error) { + rawResult, err := s.sidechainBridgeContract.Call("lastCommittedId", ethgo.Latest) + if err != nil { + return 0, err + } + + nextCommittedIndex, isOk := rawResult["0"].(*big.Int) + if !isOk { + return 0, fmt.Errorf("failed to decode next committed index") + } + + return nextCommittedIndex.Uint64() + 1, nil +} + +func buildLogsFromReceipts(entry []*types.Receipt, header *types.Header) []*types.Log { + var logs []*types.Log + + for _, taskReceipt := range entry { + for _, taskLog := range taskReceipt.Logs { + log := new(types.Log) + *log = *taskLog + + data := map[string]interface{}{ + "Hash": header.Hash, + "Number": header.Number, + } + log.Data, _ = json.Marshal(&data) + logs = append(logs, log) + } + } + + return logs +} diff --git a/consensus/polybft/system_state_test.go b/consensus/polybft/system_state_test.go new file mode 100644 index 0000000000..098733d0a0 --- /dev/null +++ b/consensus/polybft/system_state_test.go @@ -0,0 +1,280 @@ +package polybft + +import ( + "encoding/hex" + "encoding/json" + "math/big" + "testing" + + "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/state" + itrie "github.com/0xPolygon/polygon-edge/state/immutable-trie" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/abi" + "github.com/umbracle/ethgo/contract" + "github.com/umbracle/ethgo/testutil" +) + +func TestSystemState_GetValidatorSet(t *testing.T) { + t.Parallel() + + cc := &testutil.Contract{} + cc.AddCallback(func() string { + return ` + + struct Validator { + uint256[4] blsKey; + uint256 stake; + uint256 totalStake; + uint256 commission; + uint256 withdrawableRewards; + bool active; + } + + function getCurrentValidatorSet() public returns (address[] memory) { + address[] memory addresses = new address[](1); + addresses[0] = address(1); + return addresses; + } + + function getValidator(address) public returns (Validator memory){ + uint[4] memory key = [ + 1708568697487735112380375954529256823287318886168633341382922712646533763844, + 14713639476280042449606484361428781226013866637570951139712205035697871856089, + 16798350082249088544573448433070681576641749462807627179536437108134609634615, + 21427200503135995176566340351867145775962083994845221446131416289459495591422 + ]; + return Validator(key, 10, 10, 0, 0, true); + } + + ` + }) + + solcContract, err := cc.Compile() + assert.NoError(t, err) + + bin, err := hex.DecodeString(solcContract.Bin) + assert.NoError(t, err) + + transition := newTestTransition(t, nil) + + // deploy a contract + result := transition.Create2(types.Address{}, bin, big.NewInt(0), 1000000000) + assert.NoError(t, result.Err) + + provider := &stateProvider{ + transition: transition, + } + + st := NewSystemState(&PolyBFTConfig{ValidatorSetAddr: result.Address}, provider) + validators, err := st.GetValidatorSet() + assert.NoError(t, err) + assert.Equal(t, types.Address(ethgo.HexToAddress("1")), validators[0].Address) + assert.Equal(t, new(big.Int).SetUint64(10), validators[0].VotingPower) +} + +func TestSystemState_GetNextCommittedIndex(t *testing.T) { + t.Parallel() + + var sideChainBridgeABI, _ = abi.NewMethod( + "function setNextCommittedIndex(uint256 _index) public payable", + ) + + cc := &testutil.Contract{} + cc.AddCallback(func() string { + return ` + uint256 public lastCommittedId; + + function setNextCommittedIndex(uint256 _index) public payable { + lastCommittedId = _index; + }` + }) + + solcContract, err := cc.Compile() + require.NoError(t, err) + + bin, err := hex.DecodeString(solcContract.Bin) + require.NoError(t, err) + + transition := newTestTransition(t, nil) + + // deploy a contract + result := transition.Create2(types.Address{}, bin, big.NewInt(0), 1000000000) + assert.NoError(t, result.Err) + + provider := &stateProvider{ + transition: transition, + } + + systemState := NewSystemState(&PolyBFTConfig{StateReceiverAddr: result.Address}, provider) + + expectedNextCommittedIndex := uint64(45) + input, err := sideChainBridgeABI.Encode([1]interface{}{expectedNextCommittedIndex}) + assert.NoError(t, err) + + _, err = provider.Call(ethgo.Address(result.Address), input, &contract.CallOpts{}) + assert.NoError(t, err) + + nextCommittedIndex, err := systemState.GetNextCommittedIndex() + assert.NoError(t, err) + assert.Equal(t, expectedNextCommittedIndex+1, nextCommittedIndex) +} + +func TestSystemState_GetEpoch(t *testing.T) { + t.Parallel() + + setEpochMethod, err := abi.NewMethod("function setEpoch(uint256 _epochId) public payable") + require.NoError(t, err) + + cc := &testutil.Contract{} + cc.AddCallback(func() string { + return ` + uint256 public currentEpochId; + + function setEpoch(uint256 _epochId) public payable { + currentEpochId = _epochId; + } + ` + }) + + solcContract, err := cc.Compile() + require.NoError(t, err) + + bin, err := hex.DecodeString(solcContract.Bin) + require.NoError(t, err) + + transition := newTestTransition(t, nil) + + // deploy a contract + result := transition.Create2(types.Address{}, bin, big.NewInt(0), 1000000000) + assert.NoError(t, result.Err) + + provider := &stateProvider{ + transition: transition, + } + + systemState := NewSystemState(&PolyBFTConfig{ValidatorSetAddr: result.Address}, provider) + + expectedEpoch := uint64(50) + input, err := setEpochMethod.Encode([1]interface{}{expectedEpoch}) + require.NoError(t, err) + + _, err = provider.Call(ethgo.Address(result.Address), input, &contract.CallOpts{}) + require.NoError(t, err) + + epoch, err := systemState.GetEpoch() + require.NoError(t, err) + require.Equal(t, expectedEpoch, epoch) +} + +func TestStateProvider_Txn_NotSupported(t *testing.T) { + t.Parallel() + + transition := newTestTransition(t, nil) + + provider := &stateProvider{ + transition: transition, + } + + require.PanicsWithError(t, errSendTxnUnsupported.Error(), + func() { _, _ = provider.Txn(ethgo.ZeroAddress, createTestKey(t), []byte{0x1}) }) +} + +func newTestTransition(t *testing.T, alloc map[types.Address]*chain.GenesisAccount) *state.Transition { + t.Helper() + + st := itrie.NewState(itrie.NewMemoryStorage()) + + ex := state.NewExecutor(&chain.Params{ + Forks: chain.AllForksEnabled, + }, st, hclog.NewNullLogger()) + + rootHash := ex.WriteGenesis(alloc) + + ex.GetHash = func(h *types.Header) state.GetHashByNumber { + return func(i uint64) types.Hash { + return rootHash + } + } + + transition, err := ex.BeginTxn( + rootHash, + &types.Header{}, + types.ZeroAddress, + ) + assert.NoError(t, err) + + return transition +} + +func Test_buildLogsFromReceipts(t *testing.T) { + t.Parallel() + + defaultHeader := &types.Header{ + Number: 100, + } + + type args struct { + entry []*types.Receipt + header *types.Header + } + + data := map[string]interface{}{ + "Hash": defaultHeader.Hash, + "Number": defaultHeader.Number, + } + + dataArray, err := json.Marshal(&data) + require.NoError(t, err) + + tests := []struct { + name string + args args + want []*types.Log + }{ + { + name: "no entries provided", + }, + { + name: "successfully created logs", + args: args{ + entry: []*types.Receipt{ + { + Logs: []*types.Log{ + { + Address: types.BytesToAddress([]byte{0, 1}), + Topics: nil, + Data: dataArray, + }, + }, + }, + }, + header: defaultHeader, + }, + want: []*types.Log{ + { + Address: types.BytesToAddress([]byte{0, 1}), + Topics: nil, + Data: dataArray, + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + assert.EqualValuesf(t, + tt.want, + buildLogsFromReceipts(tt.args.entry, tt.args.header), + "buildLogsFromReceipts(%v, %v)", tt.args.entry, tt.args.header, + ) + }) + } +} diff --git a/consensus/polybft/transport.go b/consensus/polybft/transport.go new file mode 100644 index 0000000000..e6153db39d --- /dev/null +++ b/consensus/polybft/transport.go @@ -0,0 +1,65 @@ +package polybft + +import ( + "fmt" + + ibftProto "github.com/0xPolygon/go-ibft/messages/proto" + polybftProto "github.com/0xPolygon/polygon-edge/consensus/polybft/proto" + "github.com/0xPolygon/polygon-edge/types" + "github.com/libp2p/go-libp2p/core/peer" +) + +// BridgeTransport is an abstraction of network layer for a bridge +type BridgeTransport interface { + Multicast(msg interface{}) +} + +// subscribeToIbftTopic subscribes to ibft topic +func (p *Polybft) subscribeToIbftTopic() error { + return p.consensusTopic.Subscribe(func(obj interface{}, _ peer.ID) { + if !p.runtime.isActiveValidator() { + return + } + + msg, ok := obj.(*ibftProto.Message) + if !ok { + p.logger.Error("consensus engine: invalid type assertion for message request") + + return + } + + p.ibft.AddMessage(msg) + + p.logger.Debug( + "validator message received", + "type", msg.Type.String(), + "height", msg.GetView().Height, + "round", msg.GetView().Round, + "addr", types.BytesToAddress(msg.From).String(), + ) + }) +} + +// createTopics create all topics for a PolyBft instance +func (p *Polybft) createTopics() (err error) { + if p.consensusConfig.IsBridgeEnabled() { + p.bridgeTopic, err = p.config.Network.NewTopic(bridgeProto, &polybftProto.TransportMessage{}) + if err != nil { + return fmt.Errorf("failed to create bridge topic: %w", err) + } + } + + p.consensusTopic, err = p.config.Network.NewTopic(pbftProto, &ibftProto.Message{}) + if err != nil { + return fmt.Errorf("failed to create consensus topic: %w", err) + } + + return nil +} + +// Multicast is implementation of core.Transport interface +func (p *Polybft) Multicast(msg *ibftProto.Message) { + if err := p.consensusTopic.Publish(msg); err != nil { + p.logger.Warn("failed to multicast consensus message", "error", err) + } +} diff --git a/consensus/polybft/validator_metadata.go b/consensus/polybft/validator_metadata.go new file mode 100644 index 0000000000..39e0364538 --- /dev/null +++ b/consensus/polybft/validator_metadata.go @@ -0,0 +1,350 @@ +package polybft + +import ( + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "math/big" + "reflect" + "strings" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/crypto" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/bitmap" + "github.com/0xPolygon/polygon-edge/types" + "github.com/umbracle/ethgo/abi" + "github.com/umbracle/fastrlp" +) + +var accountSetABIType = abi.MustNewType(`tuple(tuple(address _address, uint256[4] blsKey, uint256 votingPower)[])`) + +// ValidatorMetadata represents a validator metadata (its public identity) +type ValidatorMetadata struct { + Address types.Address + BlsKey *bls.PublicKey + VotingPower *big.Int +} + +// Equals checks ValidatorMetadata equality +func (v *ValidatorMetadata) Equals(b *ValidatorMetadata) bool { + if b == nil { + return false + } + + return v.EqualAddressAndBlsKey(b) && v.VotingPower.Cmp(b.VotingPower) == 0 +} + +// EqualAddressAndBlsKey checks ValidatorMetadata equality against Address and BlsKey fields +func (v *ValidatorMetadata) EqualAddressAndBlsKey(b *ValidatorMetadata) bool { + if b == nil { + return false + } + + return v.Address == b.Address && reflect.DeepEqual(v.BlsKey, b.BlsKey) +} + +// Copy returns a deep copy of ValidatorMetadata +func (v *ValidatorMetadata) Copy() *ValidatorMetadata { + copiedBlsKey := v.BlsKey.Marshal() + blsKey, _ := bls.UnmarshalPublicKey(copiedBlsKey) + + return &ValidatorMetadata{ + Address: types.BytesToAddress(v.Address[:]), + BlsKey: blsKey, + VotingPower: new(big.Int).Set(v.VotingPower), + } +} + +// MarshalRLPWith marshals ValidatorMetadata to the RLP format +func (v *ValidatorMetadata) MarshalRLPWith(ar *fastrlp.Arena) *fastrlp.Value { + vv := ar.NewArray() + // Address + vv.Set(ar.NewBytes(v.Address.Bytes())) + // BlsKey + vv.Set(ar.NewCopyBytes(v.BlsKey.Marshal())) + // VotingPower + vv.Set(ar.NewBigInt(v.VotingPower)) + + return vv +} + +// UnmarshalRLPWith unmarshals ValidatorMetadata from the RLP format +func (v *ValidatorMetadata) UnmarshalRLPWith(val *fastrlp.Value) error { + elems, err := val.GetElems() + if err != nil { + return err + } + + if num := len(elems); num != 3 { + return fmt.Errorf("incorrect elements count to decode validator account, expected 3 but found %d", num) + } + + // Address + addressRaw, err := elems[0].GetBytes(nil) + if err != nil { + return fmt.Errorf("expected 'Address' field encoded as bytes. Error: %w", err) + } + + v.Address = types.BytesToAddress(addressRaw) + + // BlsKey + blsKeyRaw, err := elems[1].GetBytes(nil) + if err != nil { + return fmt.Errorf("expected 'BlsKey' encoded as bytes. Error: %w", err) + } + + blsKey, err := bls.UnmarshalPublicKey(blsKeyRaw) + if err != nil { + return fmt.Errorf("failed to unmarshal BLS public key. Error: %w", err) + } + + v.BlsKey = blsKey + + // VotingPower + votingPower := new(big.Int) + + err = elems[2].GetBigInt(votingPower) + if err != nil { + return fmt.Errorf("expected 'Voting power' encoded as big int. Error: %w", err) + } + + v.VotingPower = new(big.Int).Set(votingPower) + + return nil +} + +// fmt.Stringer implementation +func (v *ValidatorMetadata) String() string { + return fmt.Sprintf("Address=%v; Voting Power=%d; BLS Key=%v;", + v.Address.String(), v.VotingPower, hex.EncodeToString(v.BlsKey.Marshal())) +} + +// AccountSet is a type alias for slice of ValidatorMetadata instances +type AccountSet []*ValidatorMetadata + +// Equals compares checks if two AccountSet instances are equal (ordering is important) +func (as AccountSet) Equals(other AccountSet) bool { + if len(as) != len(other) { + return false + } + + for i := range as { + if !as[i].Equals(other[i]) { + return false + } + } + + return true +} + +// fmt.Stringer implementation +func (as AccountSet) String() string { + metadataString := make([]string, len(as)) + for i, v := range as { + metadataString[i] = v.String() + } + + return strings.Join(metadataString, "\n") +} + +// GetAddresses aggregates addresses for given AccountSet +func (as AccountSet) GetAddresses() []types.Address { + res := make([]types.Address, 0, len(as)) + for _, account := range as { + res = append(res, account.Address) + } + + return res +} + +// GetAddresses aggregates addresses as map for given AccountSet +func (as AccountSet) GetAddressesAsSet() map[types.Address]struct{} { + res := make(map[types.Address]struct{}, len(as)) + for _, account := range as { + res[account.Address] = struct{}{} + } + + return res +} + +// GetBlsKeys aggregates public BLS keys for given AccountSet +func (as AccountSet) GetBlsKeys() []*bls.PublicKey { + res := make([]*bls.PublicKey, 0, len(as)) + for _, account := range as { + res = append(res, account.BlsKey) + } + + return res +} + +// Len returns length of AccountSet +func (as AccountSet) Len() int { + return len(as) +} + +// ContainsNodeID checks whether ValidatorMetadata with given nodeID is present in the AccountSet +func (as AccountSet) ContainsNodeID(nodeID string) bool { + for _, validator := range as { + if validator.Address.String() == nodeID { + return true + } + } + + return false +} + +// ContainsAddress checks whether ValidatorMetadata with given address is present in the AccountSet +func (as AccountSet) ContainsAddress(address types.Address) bool { + return as.Index(address) != -1 +} + +// Index returns index of the given ValidatorMetadata, identified by address within the AccountSet. +// If given ValidatorMetadata is not present, it returns -1. +func (as AccountSet) Index(addr types.Address) int { + for indx, validator := range as { + if validator.Address == addr { + return indx + } + } + + return -1 +} + +// Copy returns deep copy of AccountSet +func (as AccountSet) Copy() AccountSet { + copiedAccs := make([]*ValidatorMetadata, as.Len()) + for i, acc := range as { + copiedAccs[i] = acc.Copy() + } + + return AccountSet(copiedAccs) +} + +// Hash returns hash value of the AccountSet +func (as AccountSet) Hash() (types.Hash, error) { + abiEncoded, err := accountSetABIType.Encode([]interface{}{as.ToAPIBinding()}) + if err != nil { + return types.ZeroHash, err + } + + return types.BytesToHash(crypto.Keccak256(abiEncoded)), nil +} + +// ToAPIBinding converts AccountSet to slice of contract api stubs to be encoded +func (as AccountSet) ToAPIBinding() []*contractsapi.Validator { + apiBinding := make([]*contractsapi.Validator, len(as)) + for i, v := range as { + apiBinding[i] = &contractsapi.Validator{ + Address: v.Address, + BlsKey: v.BlsKey.ToBigInt(), + VotingPower: new(big.Int).Set(v.VotingPower), + } + } + + return apiBinding +} + +// GetValidatorMetadata tries to retrieve validator account metadata by given address from the account set. +// It returns nil if such account is not found. +func (as AccountSet) GetValidatorMetadata(address types.Address) *ValidatorMetadata { + i := as.Index(address) + if i == -1 { + return nil + } + + return as[i] +} + +// GetFilteredValidators returns filtered validators based on provided bitmap. +// Filtered validators will contain validators whose index corresponds +// to the position in bitmap which has value set to 1. +func (as AccountSet) GetFilteredValidators(bitmap bitmap.Bitmap) (AccountSet, error) { + var filteredValidators AccountSet + if len(as) == 0 { + return filteredValidators, nil + } + + if bitmap.Len() > uint64(len(as)) { + for i := len(as); i < int(bitmap.Len()); i++ { + if bitmap.IsSet(uint64(i)) { + return filteredValidators, errors.New("invalid bitmap filter provided") + } + } + } + + for i, validator := range as { + if bitmap.IsSet(uint64(i)) { + filteredValidators = append(filteredValidators, validator) + } + } + + return filteredValidators, nil +} + +// ApplyDelta receives ValidatorSetDelta and applies it to the values from the current AccountSet +// (removes the ones marked for deletion and adds the one which are being added by delta) +// Function returns new AccountSet with old and new data merged. AccountSet is immutable! +func (as AccountSet) ApplyDelta(validatorsDelta *ValidatorSetDelta) (AccountSet, error) { + if validatorsDelta == nil || validatorsDelta.IsEmpty() { + return as.Copy(), nil + } + + // Figure out which validators from the existing set are not marked for deletion. + // Those should be kept in the snapshot. + validators := make(AccountSet, 0) + + for i, validator := range as { + // If a validator is not in the Removed set, or it is in the Removed set + // but it exists in the Added set as well (which should never happen), + // the validator should remain in the validator set. + if !validatorsDelta.Removed.IsSet(uint64(i)) || + validatorsDelta.Added.ContainsAddress(validator.Address) { + validators = append(validators, validator) + } + } + + // Append added validators + for _, addedValidator := range validatorsDelta.Added { + if validators.ContainsAddress(addedValidator.Address) { + return nil, fmt.Errorf("validator %v is already present in the validators snapshot", addedValidator.Address.String()) + } + + validators = append(validators, addedValidator) + } + + // Handle updated validators (find them in the validators slice and insert to appropriate index) + for _, updatedValidator := range validatorsDelta.Updated { + validatorIndex := validators.Index(updatedValidator.Address) + if validatorIndex == -1 { + return nil, fmt.Errorf("incorrect delta provided: validator %s is marked as updated but not found in the validators", + updatedValidator.Address) + } + + validators[validatorIndex] = updatedValidator + } + + return validators, nil +} + +// Marshal marshals AccountSet to JSON +func (as AccountSet) Marshal() ([]byte, error) { + return json.Marshal(as) +} + +// Unmarshal unmarshals AccountSet from JSON +func (as *AccountSet) Unmarshal(b []byte) error { + return json.Unmarshal(b, as) +} + +// GetTotalVotingPower calculates sum of voting power for each validator in the AccountSet +func (as *AccountSet) GetTotalVotingPower() *big.Int { + totalVotingPower := big.NewInt(0) + for _, v := range *as { + totalVotingPower = totalVotingPower.Add(totalVotingPower, v.VotingPower) + } + + return totalVotingPower +} diff --git a/consensus/polybft/validator_metadata_test.go b/consensus/polybft/validator_metadata_test.go new file mode 100644 index 0000000000..eb7f1010f1 --- /dev/null +++ b/consensus/polybft/validator_metadata_test.go @@ -0,0 +1,269 @@ +package polybft + +import ( + "math/big" + "testing" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/bitmap" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestValidatorMetadata_Equals(t *testing.T) { + t.Parallel() + + v := newTestValidator("A", 10) + validatorAcc := v.ValidatorMetadata() + // proper validator metadata instance doesn't equal to nil + require.False(t, validatorAcc.Equals(nil)) + // same instances of validator metadata are equal + require.True(t, validatorAcc.Equals(v.ValidatorMetadata())) + + // update voting power => validator metadata instances aren't equal + validatorAcc.VotingPower = new(big.Int).SetInt64(50) + require.False(t, validatorAcc.Equals(v.ValidatorMetadata())) +} + +func TestValidatorMetadata_EqualAddressAndBlsKey(t *testing.T) { + t.Parallel() + + v := newTestValidator("A", 10) + validatorAcc := v.ValidatorMetadata() + // proper validator metadata instance doesn't equal to nil + require.False(t, validatorAcc.EqualAddressAndBlsKey(nil)) + // same instances of validator metadata are equal + require.True(t, validatorAcc.EqualAddressAndBlsKey(v.ValidatorMetadata())) + + // update voting power => validator metadata instances aren't equal + validatorAcc.Address = types.BytesToAddress(generateRandomBytes(t)) + require.False(t, validatorAcc.EqualAddressAndBlsKey(v.ValidatorMetadata())) +} + +func TestAccountSet_GetAddresses(t *testing.T) { + t.Parallel() + + address1, address2, address3 := types.Address{4, 3}, types.Address{68, 123}, types.Address{168, 123} + ac := AccountSet{ + &ValidatorMetadata{Address: address1}, + &ValidatorMetadata{Address: address2}, + &ValidatorMetadata{Address: address3}, + } + rs := ac.GetAddresses() + assert.Len(t, rs, 3) + assert.Equal(t, address1, rs[0]) + assert.Equal(t, address2, rs[1]) + assert.Equal(t, address3, rs[2]) +} + +func TestAccountSet_GetBlsKeys(t *testing.T) { + t.Parallel() + + keys, err := bls.CreateRandomBlsKeys(3) + assert.NoError(t, err) + + key1, key2, key3 := keys[0], keys[1], keys[2] + ac := AccountSet{ + &ValidatorMetadata{BlsKey: key1.PublicKey()}, + &ValidatorMetadata{BlsKey: key2.PublicKey()}, + &ValidatorMetadata{BlsKey: key3.PublicKey()}, + } + rs := ac.GetBlsKeys() + assert.Len(t, rs, 3) + assert.Equal(t, key1.PublicKey(), rs[0]) + assert.Equal(t, key2.PublicKey(), rs[1]) + assert.Equal(t, key3.PublicKey(), rs[2]) +} + +func TestAccountSet_IndexContainsAddressesAndContainsNodeId(t *testing.T) { + t.Parallel() + + const count = 10 + + dummy := types.Address{2, 3, 4} + validators := newTestValidators(count).getPublicIdentities() + addresses := [count]types.Address{} + + for i, validator := range validators { + addresses[i] = validator.Address + } + + for i, a := range addresses { + assert.Equal(t, i, validators.Index(a)) + assert.True(t, validators.ContainsAddress(a)) + assert.True(t, validators.ContainsNodeID(a.String())) + } + + assert.Equal(t, -1, validators.Index(dummy)) + assert.False(t, validators.ContainsAddress(dummy)) + assert.False(t, validators.ContainsNodeID(dummy.String())) +} + +func TestAccountSet_Len(t *testing.T) { + t.Parallel() + + const count = 10 + + ac := AccountSet{} + + for i := 0; i < count; i++ { + ac = append(ac, &ValidatorMetadata{}) + assert.Equal(t, i+1, ac.Len()) + } +} + +func TestAccountSet_ApplyDelta(t *testing.T) { + t.Parallel() + + type Step struct { + added []string + updated map[string]uint64 + removed []uint64 + expected map[string]uint64 + errMsg string + } + + cases := []struct { + name string + steps []*Step + }{ + { + name: "Basic", + steps: []*Step{ + { + []string{"A", "B", "C", "D"}, + nil, + nil, + map[string]uint64{"A": 1, "B": 1, "C": 1, "D": 1}, + "", + }, + { + // add two new validators and remove 3 (one does not exists) + // update voting powers to subset of validators + // (two of them added in the previous step and one added in the current one) + []string{"E", "F"}, + map[string]uint64{"A": 30, "D": 10, "E": 5}, + []uint64{1, 2, 5}, + map[string]uint64{"A": 30, "D": 10, "E": 5, "F": 1}, + "", + }, + }, + }, + { + name: "AddRemoveSameValidator", + steps: []*Step{ + { + []string{"A"}, + nil, + []uint64{0}, + map[string]uint64{"A": 1}, + "", + }, + }, + }, + { + name: "AddSameValidatorTwice", + steps: []*Step{ + { + []string{"A", "A"}, + nil, + nil, + nil, + "is already present in the validators snapshot", + }, + }, + }, + { + name: "UpdateNonExistingValidator", + steps: []*Step{ + { + nil, + map[string]uint64{"B": 5}, + nil, + nil, + "incorrect delta provided: validator", + }, + }, + }, + } + + for _, cc := range cases { + cc := cc + t.Run(cc.name, func(t *testing.T) { + t.Parallel() + + snapshot := AccountSet{} + // Add a couple of validators to the snapshot => validators are present in the snapshot after applying such delta + vals := newTestValidatorsWithAliases([]string{"A", "B", "C", "D", "E", "F"}) + + for _, step := range cc.steps { + addedValidators := AccountSet{} + if step.added != nil { + addedValidators = vals.getPublicIdentities(step.added...) + } + delta := &ValidatorSetDelta{ + Added: addedValidators, + Removed: bitmap.Bitmap{}, + } + for _, i := range step.removed { + delta.Removed.Set(i) + } + + // update voting powers + delta.Updated = vals.updateVotingPowers(step.updated) + + // apply delta + var err error + snapshot, err = snapshot.ApplyDelta(delta) + if step.errMsg != "" { + require.ErrorContains(t, err, step.errMsg) + require.Nil(t, snapshot) + + return + } + require.NoError(t, err) + + // validate validator set + require.Equal(t, len(step.expected), snapshot.Len()) + for validatorAlias, votingPower := range step.expected { + v := vals.getValidator(validatorAlias).ValidatorMetadata() + require.True(t, snapshot.ContainsAddress(v.Address), "validator '%s' not found in snapshot", validatorAlias) + require.Equal(t, new(big.Int).SetUint64(votingPower), v.VotingPower) + } + } + }) + } +} + +func TestAccountSet_ApplyEmptyDelta(t *testing.T) { + t.Parallel() + + v := newTestValidatorsWithAliases([]string{"A", "B", "C", "D", "E", "F"}) + validatorAccs := v.getPublicIdentities() + validators, err := validatorAccs.ApplyDelta(nil) + require.NoError(t, err) + require.Equal(t, validatorAccs, validators) +} + +func TestAccountSet_Hash(t *testing.T) { + t.Parallel() + + t.Run("Hash non-empty account set", func(t *testing.T) { + t.Parallel() + + v := newTestValidatorsWithAliases([]string{"A", "B", "C", "D", "E", "F"}) + hash, err := v.getPublicIdentities().Hash() + require.NoError(t, err) + require.NotEqual(t, types.ZeroHash, hash) + }) + + t.Run("Hash empty account set", func(t *testing.T) { + t.Parallel() + + empty := AccountSet{} + hash, err := empty.Hash() + require.NoError(t, err) + require.NotEqual(t, types.ZeroHash, hash) + }) +} diff --git a/consensus/polybft/validator_set.go b/consensus/polybft/validator_set.go new file mode 100644 index 0000000000..ff8d893ab4 --- /dev/null +++ b/consensus/polybft/validator_set.go @@ -0,0 +1,101 @@ +package polybft + +import ( + "math/big" + + "github.com/0xPolygon/polygon-edge/helper/common" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" +) + +// ValidatorSet interface of the current validator set +type ValidatorSet interface { + // Includes check if given address is among the current validator set + Includes(address types.Address) bool + + // Len returns the size of the validator set + Len() int + + // Accounts returns the list of the ValidatorMetadata + Accounts() AccountSet + + // checks if submitted signers have reached quorum + HasQuorum(signers map[types.Address]struct{}) bool +} + +type validatorSet struct { + // validators represents current list of validators + validators AccountSet + + // votingPowerMap represents voting powers per validator address + votingPowerMap map[types.Address]*big.Int + + // totalVotingPower is sum of active validator set + totalVotingPower *big.Int + + // quorumSize is 2/3 super-majority of totalVotingPower + quorumSize *big.Int + + // logger instance + logger hclog.Logger +} + +// NewValidatorSet creates a new validator set. +func NewValidatorSet(valz AccountSet, logger hclog.Logger) *validatorSet { + totalVotingPower := valz.GetTotalVotingPower() + quorumSize := getQuorumSize(totalVotingPower) + + votingPowerMap := make(map[types.Address]*big.Int, len(valz)) + for _, val := range valz { + votingPowerMap[val.Address] = val.VotingPower + } + + return &validatorSet{ + validators: valz, + votingPowerMap: votingPowerMap, + totalVotingPower: totalVotingPower, + quorumSize: quorumSize, + logger: logger.Named("validator_set"), + } +} + +// HasQuorum determines if there is quorum of enough signers reached, +// based on its voting power and quorum size +func (vs validatorSet) HasQuorum(signers map[types.Address]struct{}) bool { + aggregateVotingPower := big.NewInt(0) + + for address := range signers { + if votingPower := vs.votingPowerMap[address]; votingPower != nil { + _ = aggregateVotingPower.Add(aggregateVotingPower, votingPower) + } + } + + hasQuorum := aggregateVotingPower.Cmp(vs.quorumSize) >= 0 + + vs.logger.Debug("HasQuorum", + "signers", len(signers), + "signers voting power", aggregateVotingPower, + "hasQuorum", hasQuorum) + + return hasQuorum +} + +func (vs validatorSet) Accounts() AccountSet { + return vs.validators +} + +func (vs validatorSet) Includes(address types.Address) bool { + return vs.validators.ContainsAddress(address) +} + +func (vs validatorSet) Len() int { + return vs.validators.Len() +} + +// getQuorumSize calculates quorum size as 2/3 super-majority of provided total voting power +func getQuorumSize(totalVotingPower *big.Int) *big.Int { + quorum := new(big.Int) + quorum.Mul(totalVotingPower, big.NewInt(2)) + + return common.BigIntDivCeil(quorum, big.NewInt(3)) +} diff --git a/consensus/polybft/validator_set_test.go b/consensus/polybft/validator_set_test.go new file mode 100644 index 0000000000..de807a522b --- /dev/null +++ b/consensus/polybft/validator_set_test.go @@ -0,0 +1,53 @@ +package polybft + +import ( + "math/big" + "testing" + + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/require" +) + +func TestValidatorSet_HasQuorum(t *testing.T) { + t.Parallel() + + // enough signers for quorum (2/3 super-majority of validators are signers) + validators := newTestValidatorsWithAliases([]string{"A", "B", "C", "D", "E", "F", "G"}) + vs := validators.toValidatorSet() + + signers := make(map[types.Address]struct{}) + + validators.iterAcct([]string{"A", "B", "C", "D", "E"}, func(v *testValidator) { + signers[v.Address()] = struct{}{} + }) + + require.True(t, vs.HasQuorum(signers)) + + // not enough signers for quorum (less than 2/3 super-majority of validators are signers) + signers = make(map[types.Address]struct{}) + + validators.iterAcct([]string{"A", "B", "C", "D"}, func(v *testValidator) { + signers[v.Address()] = struct{}{} + }) + require.False(t, vs.HasQuorum(signers)) +} + +func TestValidatorSet_getQuorumSize(t *testing.T) { + t.Parallel() + + cases := []struct { + totalVotingPower int64 + expectedQuorumSize int64 + }{ + {10, 7}, + {12, 8}, + {13, 9}, + {50, 34}, + {100, 67}, + } + + for _, c := range cases { + quorumSize := getQuorumSize(big.NewInt(c.totalVotingPower)) + require.Equal(t, c.expectedQuorumSize, quorumSize.Int64()) + } +} diff --git a/consensus/polybft/validators_snapshot.go b/consensus/polybft/validators_snapshot.go new file mode 100644 index 0000000000..212e2051d8 --- /dev/null +++ b/consensus/polybft/validators_snapshot.go @@ -0,0 +1,323 @@ +package polybft + +import ( + "errors" + "fmt" + "sync" + + "github.com/0xPolygon/polygon-edge/blockchain" + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" +) + +type validatorSnapshot struct { + Epoch uint64 `json:"epoch"` + EpochEndingBlock uint64 `json:"epochEndingBlock"` + Snapshot AccountSet `json:"snapshot"` +} + +func (vs *validatorSnapshot) copy() *validatorSnapshot { + copiedAccountSet := vs.Snapshot.Copy() + + return &validatorSnapshot{ + Epoch: vs.Epoch, + EpochEndingBlock: vs.EpochEndingBlock, + Snapshot: copiedAccountSet, + } +} + +// TODO: Should we switch validators snapshot to Edge implementation? (validators/store/snapshot/snapshot.go) +type validatorsSnapshotCache struct { + snapshots map[uint64]*validatorSnapshot + state *State + blockchain blockchainBackend + lock sync.Mutex + logger hclog.Logger +} + +// newValidatorsSnapshotCache initializes a new instance of validatorsSnapshotCache +func newValidatorsSnapshotCache( + logger hclog.Logger, state *State, blockchain blockchainBackend, +) *validatorsSnapshotCache { + return &validatorsSnapshotCache{ + snapshots: map[uint64]*validatorSnapshot{}, + state: state, + blockchain: blockchain, + logger: logger.Named("validators_snapshot"), + } +} + +// GetSnapshot tries to retrieve the most recent cached snapshot (if any) and +// applies pending validator set deltas to it. +// Otherwise, it builds a snapshot from scratch and applies pending validator set deltas. +func (v *validatorsSnapshotCache) GetSnapshot(blockNumber uint64, parents []*types.Header) (AccountSet, error) { + v.lock.Lock() + defer v.lock.Unlock() + + _, extra, err := getBlockData(blockNumber, v.blockchain) + if err != nil { + return nil, err + } + + isEpochEndingBlock, err := isEpochEndingBlock(blockNumber, extra, v.blockchain) + if err != nil && !errors.Is(err, blockchain.ErrNoBlock) { + // if there is no block after given block, we assume its not epoch ending block + // but, it's a regular use case, and we should not stop the snapshot calculation + // because there are cases we need the snapshot for the latest block in chain + return nil, err + } + + epochToGetSnapshot := extra.Checkpoint.EpochNumber + if !isEpochEndingBlock { + epochToGetSnapshot-- + } + + v.logger.Trace("Retrieving snapshot started...", "Block", blockNumber, "Epoch", epochToGetSnapshot) + + latestValidatorSnapshot, err := v.getLastCachedSnapshot(epochToGetSnapshot) + if err != nil { + return nil, err + } + + if latestValidatorSnapshot != nil && latestValidatorSnapshot.Epoch == epochToGetSnapshot { + // we have snapshot for required block (epoch) in cache + return latestValidatorSnapshot.Snapshot, nil + } + + if latestValidatorSnapshot == nil { + // Haven't managed to retrieve snapshot for any epoch from the cache. + // Build snapshot from the scratch, by applying delta from the genesis block. + genesisBlockSnapshot, err := v.computeSnapshot(nil, 0, parents) + if err != nil { + return nil, fmt.Errorf("failed to compute snapshot for epoch 0: %w", err) + } + + err = v.storeSnapshot(genesisBlockSnapshot) + if err != nil { + return nil, fmt.Errorf("failed to store validators snapshot for epoch 0: %w", err) + } + + latestValidatorSnapshot = genesisBlockSnapshot + + v.logger.Trace("Built validators snapshot for genesis block") + } + + deltasCount := 0 + + v.logger.Trace("Applying deltas started...", "LatestSnapshotEpoch", latestValidatorSnapshot.Epoch) + + // Create the snapshot for the desired block (epoch) by incrementally applying deltas to the latest stored snapshot + for latestValidatorSnapshot.Epoch < epochToGetSnapshot { + nextEpochEndBlockNumber, err := v.getNextEpochEndingBlock(latestValidatorSnapshot.EpochEndingBlock) + if err != nil { + return nil, fmt.Errorf("failed to get the epoch ending block for epoch: %d. Error: %w", + latestValidatorSnapshot.Epoch+1, err) + } + + v.logger.Trace("Applying delta", "epochEndBlock", nextEpochEndBlockNumber) + + intermediateSnapshot, err := v.computeSnapshot(latestValidatorSnapshot, nextEpochEndBlockNumber, parents) + if err != nil { + return nil, fmt.Errorf("failed to compute snapshot for epoch %d: %w", latestValidatorSnapshot.Epoch+1, err) + } + + latestValidatorSnapshot = intermediateSnapshot + if err = v.storeSnapshot(latestValidatorSnapshot); err != nil { + return nil, fmt.Errorf("failed to store validators snapshot for epoch %d: %w", latestValidatorSnapshot.Epoch, err) + } + + deltasCount++ + } + + v.logger.Trace( + fmt.Sprintf("Applied %d delta(s) to the validators snapshot", deltasCount), + "Epoch", latestValidatorSnapshot.Epoch, + ) + + if err := v.cleanup(); err != nil { + // error on cleanup should not block or fail any action + v.logger.Error("could not clean validator snapshots from cache and db", "err", err) + } + + return latestValidatorSnapshot.Snapshot, nil +} + +// computeSnapshot gets desired block header by block number, extracts its extra and applies given delta to the snapshot +func (v *validatorsSnapshotCache) computeSnapshot( + existingSnapshot *validatorSnapshot, + nextEpochEndBlockNumber uint64, + parents []*types.Header, +) (*validatorSnapshot, error) { + var header *types.Header + + v.logger.Trace("Compute snapshot started...", "BlockNumber", nextEpochEndBlockNumber) + + if len(parents) > 0 { + for i := len(parents) - 1; i >= 0; i-- { + parentHeader := parents[i] + if parentHeader.Number == nextEpochEndBlockNumber { + v.logger.Trace("Compute snapshot. Found header in parents", "Header", parentHeader.Number) + header = parentHeader + + break + } + } + } + + if header == nil { + var ok bool + + header, ok = v.blockchain.GetHeaderByNumber(nextEpochEndBlockNumber) + if !ok { + return nil, fmt.Errorf("unknown block. Block number=%v", nextEpochEndBlockNumber) + } + } + + extra, err := GetIbftExtra(header.ExtraData) + if err != nil { + return nil, fmt.Errorf("failed to decode extra from the block#%d: %w", header.Number, err) + } + + var ( + snapshot AccountSet + snapshotEpoch uint64 + ) + + if existingSnapshot == nil { + snapshot = AccountSet{} + } else { + snapshot = existingSnapshot.Snapshot + snapshotEpoch = existingSnapshot.Epoch + 1 + } + + snapshot, err = snapshot.ApplyDelta(extra.Validators) + if err != nil { + return nil, fmt.Errorf("failed to apply delta to the validators snapshot, block#%d: %w", header.Number, err) + } + + v.logger.Trace("Computed snapshot", + "blockNumber", nextEpochEndBlockNumber, + "snapshot", snapshot, + "delta", extra.Validators) + + return &validatorSnapshot{ + Epoch: snapshotEpoch, + EpochEndingBlock: nextEpochEndBlockNumber, + Snapshot: snapshot, + }, nil +} + +// storeSnapshot stores given snapshot to the in-memory cache and database +func (v *validatorsSnapshotCache) storeSnapshot(snapshot *validatorSnapshot) error { + copySnap := snapshot.copy() + v.snapshots[copySnap.Epoch] = copySnap + + if err := v.state.insertValidatorSnapshot(copySnap); err != nil { + return fmt.Errorf("failed to insert validator snapshot for epoch %d to the database: %w", copySnap.Epoch, err) + } + + v.logger.Trace("Store snapshot", "Snapshots", v.snapshots) + + return nil +} + +// Cleanup cleans the validators cache in memory and db +func (v *validatorsSnapshotCache) cleanup() error { + if len(v.snapshots) >= validatorSnapshotLimit { + latestEpoch := uint64(0) + + for e := range v.snapshots { + if e > latestEpoch { + latestEpoch = e + } + } + + startEpoch := latestEpoch + cache := make(map[uint64]*validatorSnapshot, numberOfSnapshotsToLeaveInMemory) + + for i := 0; i < numberOfSnapshotsToLeaveInMemory; i++ { + if snapshot, exists := v.snapshots[startEpoch]; exists { + cache[startEpoch] = snapshot + } + + startEpoch-- + } + + v.snapshots = cache + + return v.state.cleanValidatorSnapshotsFromDB(latestEpoch) + } + + return nil +} + +// getLastCachedSnapshot gets the latest snapshot cached +// If it doesn't have snapshot cached for desired epoch, it will return the latest one it has +func (v *validatorsSnapshotCache) getLastCachedSnapshot(currentEpoch uint64) (*validatorSnapshot, error) { + cachedSnapshot := v.snapshots[currentEpoch] + if cachedSnapshot != nil { + return cachedSnapshot, nil + } + + // if we do not have a snapshot in memory for given epoch, we will get the latest one we have + for ; currentEpoch >= 0; currentEpoch-- { + cachedSnapshot = v.snapshots[currentEpoch] + if cachedSnapshot != nil { + v.logger.Trace("Found snapshot in memory cache", "Epoch", currentEpoch) + + break + } + + if currentEpoch == 0 { // prevent uint64 underflow + break + } + } + + dbSnapshot, err := v.state.getLastSnapshot() + if err != nil { + return nil, err + } + + if dbSnapshot != nil { + // if we do not have any snapshot in memory, or db snapshot is newer than the one in memory + // return the one from db + if cachedSnapshot == nil || dbSnapshot.Epoch > cachedSnapshot.Epoch { + cachedSnapshot = dbSnapshot + // save it in cache as well, since it doesn't exist + v.snapshots[dbSnapshot.Epoch] = dbSnapshot.copy() + } + } + + return cachedSnapshot, nil +} + +// getNextEpochEndingBlock gets the epoch ending block of a newer epoch +// It start checking the blocks from the provided epoch ending block of the previous epoch +func (v *validatorsSnapshotCache) getNextEpochEndingBlock(latestEpochEndingBlock uint64) (uint64, error) { + blockNumber := latestEpochEndingBlock + 1 // get next block + + _, extra, err := getBlockData(blockNumber, v.blockchain) + if err != nil { + return 0, err + } + + startEpoch := extra.Checkpoint.EpochNumber + epoch := startEpoch + + for startEpoch == epoch { + blockNumber++ + + _, extra, err = getBlockData(blockNumber, v.blockchain) + if err != nil { + if errors.Is(err, blockchain.ErrNoBlock) { + return blockNumber - 1, nil + } + + return 0, err + } + + epoch = extra.Checkpoint.EpochNumber + } + + return blockNumber - 1, nil +} diff --git a/consensus/polybft/validators_snapshot_test.go b/consensus/polybft/validators_snapshot_test.go new file mode 100644 index 0000000000..058efac7a7 --- /dev/null +++ b/consensus/polybft/validators_snapshot_test.go @@ -0,0 +1,294 @@ +package polybft + +import ( + "fmt" + "testing" + + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestValidatorsSnapshotCache_GetSnapshot_Build(t *testing.T) { + t.Parallel() + assertions := require.New(t) + + const ( + totalValidators = 10 + validatorSetSize = 5 + epochSize = uint64(10) + ) + + allValidators := newTestValidators(totalValidators).getPublicIdentities() + + var oddValidators, evenValidators AccountSet + + for i := 0; i < totalValidators; i++ { + if i%2 == 0 { + evenValidators = append(evenValidators, allValidators[i]) + } else { + oddValidators = append(oddValidators, allValidators[i]) + } + } + + headersMap := &testHeadersMap{headersByNumber: make(map[uint64]*types.Header)} + + createHeaders(t, headersMap, 0, epochSize-1, 1, nil, allValidators[:validatorSetSize]) + createHeaders(t, headersMap, epochSize, 2*epochSize-1, 2, allValidators[:validatorSetSize], allValidators[validatorSetSize:]) + createHeaders(t, headersMap, 2*epochSize, 3*epochSize-1, 3, allValidators[validatorSetSize:], oddValidators) + createHeaders(t, headersMap, 3*epochSize, 4*epochSize-1, 4, oddValidators, evenValidators) + + var cases = []struct { + blockNumber uint64 + expectedSnapshot AccountSet + validatorsOverlap bool + parents []*types.Header + }{ + {4, allValidators[:validatorSetSize], false, nil}, + {1 * epochSize, allValidators[validatorSetSize:], false, nil}, + {13, allValidators[validatorSetSize:], false, nil}, + {27, oddValidators, true, nil}, + {36, evenValidators, true, nil}, + {4, allValidators[:validatorSetSize], false, headersMap.getHeaders()}, + {13, allValidators[validatorSetSize:], false, headersMap.getHeaders()}, + {27, oddValidators, true, headersMap.getHeaders()}, + {36, evenValidators, true, headersMap.getHeaders()}, + } + + blockchainMock := new(blockchainMock) + blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headersMap.getHeader) + + testValidatorsCache := &testValidatorsCache{ + validatorsSnapshotCache: newValidatorsSnapshotCache(hclog.NewNullLogger(), newTestState(t), blockchainMock), + } + + for _, c := range cases { + snapshot, err := testValidatorsCache.GetSnapshot(c.blockNumber, c.parents) + + assertions.NoError(err) + assertions.Len(snapshot, c.expectedSnapshot.Len()) + + if c.validatorsOverlap { + for _, validator := range c.expectedSnapshot { + // Order of validators is not preserved, because there are overlapping between validators set. + // In that case, at the beginning of the set are the ones preserved from the previous validator set. + // Newly validators are added to the end after the one from previous validator set. + assertions.True(snapshot.ContainsAddress(validator.Address)) + } + } else { + assertions.Equal(c.expectedSnapshot, snapshot) + } + + assertions.NoError(testValidatorsCache.cleanValidatorsCache()) + + if c.parents != nil { + blockchainMock.AssertNotCalled(t, "GetHeaderByNumber") + } + } +} + +func TestValidatorsSnapshotCache_GetSnapshot_FetchFromCache(t *testing.T) { + t.Parallel() + require := require.New(t) + + const ( + totalValidators = 10 + validatorSetSize = 5 + ) + + allValidators := newTestValidators(totalValidators).getPublicIdentities() + epochOneValidators := AccountSet{allValidators[0], allValidators[len(allValidators)-1]} + epochTwoValidators := allValidators[1 : len(allValidators)-2] + + headersMap := &testHeadersMap{headersByNumber: make(map[uint64]*types.Header)} + createHeaders(t, headersMap, 0, 9, 1, nil, allValidators) + createHeaders(t, headersMap, 10, 19, 2, allValidators, epochOneValidators) + createHeaders(t, headersMap, 20, 29, 3, epochOneValidators, epochTwoValidators) + + blockchainMock := new(blockchainMock) + blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headersMap.getHeader) + + testValidatorsCache := &testValidatorsCache{ + validatorsSnapshotCache: newValidatorsSnapshotCache(hclog.NewNullLogger(), newTestState(t), blockchainMock), + } + + require.NoError(testValidatorsCache.storeSnapshot(&validatorSnapshot{1, 10, epochOneValidators})) + require.NoError(testValidatorsCache.storeSnapshot(&validatorSnapshot{2, 20, epochTwoValidators})) + + // Fetch snapshot from in memory cache + snapshot, err := testValidatorsCache.GetSnapshot(10, nil) + require.NoError(err) + require.Equal(epochOneValidators, snapshot) + + // Invalidate in memory cache + testValidatorsCache.snapshots = map[uint64]*validatorSnapshot{} + require.NoError(testValidatorsCache.state.removeAllValidatorSnapshots()) + // Fetch snapshot from database + snapshot, err = testValidatorsCache.GetSnapshot(10, nil) + require.NoError(err) + require.Equal(epochOneValidators, snapshot) + + snapshot, err = testValidatorsCache.GetSnapshot(20, nil) + require.NoError(err) + require.Equal(epochTwoValidators, snapshot) +} + +func TestValidatorsSnapshotCache_Cleanup(t *testing.T) { + t.Parallel() + require := require.New(t) + + blockchainMock := new(blockchainMock) + cache := &testValidatorsCache{ + validatorsSnapshotCache: newValidatorsSnapshotCache(hclog.NewNullLogger(), newTestState(t), blockchainMock), + } + snapshot := newTestValidators(3).getPublicIdentities() + maxEpoch := uint64(0) + + for i := uint64(0); i < validatorSnapshotLimit; i++ { + require.NoError(cache.storeSnapshot(&validatorSnapshot{i, i * 10, snapshot})) + maxEpoch++ + } + + require.NoError(cache.cleanup()) + + // assertions for remaining snapshots in the in memory cache + require.Len(cache.snapshots, numberOfSnapshotsToLeaveInMemory) + + currentEpoch := maxEpoch + + for i := 0; i < numberOfSnapshotsToLeaveInMemory; i++ { + currentEpoch-- + currentSnapshot, snapExists := cache.snapshots[currentEpoch] + require.True(snapExists, fmt.Sprintf("failed to fetch in memory snapshot for epoch %d", currentEpoch)) + require.Equal(snapshot, currentSnapshot.Snapshot, fmt.Sprintf("snapshots for epoch %d are not equal", currentEpoch)) + } + + // assertions for remaining snapshots in database + require.Equal(cache.state.validatorSnapshotsDBStats().KeyN, numberOfSnapshotsToLeaveInDB) + + currentEpoch = maxEpoch + + for i := 0; i < numberOfSnapshotsToLeaveInDB; i++ { + currentEpoch-- + currentSnapshot, err := cache.state.getValidatorSnapshot(currentEpoch) + require.NoError(err, fmt.Sprintf("failed to fetch database snapshot for epoch %d", currentEpoch)) + require.Equal(snapshot, currentSnapshot.Snapshot, fmt.Sprintf("snapshots for epoch %d are not equal", currentEpoch)) + } +} + +func TestValidatorsSnapshotCache_ComputeSnapshot_UnknownBlock(t *testing.T) { + t.Parallel() + assertions := assert.New(t) + + const ( + totalValidators = 15 + validatorSetSize = totalValidators / 2 + epochSize = uint64(10) + ) + + allValidators := newTestValidators(totalValidators).getPublicIdentities() + headersMap := &testHeadersMap{} + headersMap.addHeader(createValidatorDeltaHeader(t, 0, 0, nil, allValidators[:validatorSetSize])) + headersMap.addHeader(createValidatorDeltaHeader(t, 1*epochSize, 1, allValidators[:validatorSetSize], allValidators[validatorSetSize:])) + + blockchainMock := new(blockchainMock) + blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headersMap.getHeader) + + testValidatorsCache := &testValidatorsCache{ + validatorsSnapshotCache: newValidatorsSnapshotCache(hclog.NewNullLogger(), newTestState(t), blockchainMock), + } + + snapshot, err := testValidatorsCache.computeSnapshot(nil, 5*epochSize, nil) + assertions.Nil(snapshot) + assertions.ErrorContains(err, "unknown block. Block number=50") +} + +func TestValidatorsSnapshotCache_ComputeSnapshot_IncorrectExtra(t *testing.T) { + t.Parallel() + assertions := assert.New(t) + + const ( + totalValidators = 6 + validatorSetSize = totalValidators / 2 + epochSize = uint64(10) + ) + + allValidators := newTestValidators(totalValidators).getPublicIdentities() + headersMap := &testHeadersMap{} + invalidHeader := createValidatorDeltaHeader(t, 1*epochSize, 1, allValidators[:validatorSetSize], allValidators[validatorSetSize:]) + invalidHeader.ExtraData = []byte{0x2, 0x7} + headersMap.addHeader(invalidHeader) + + blockchainMock := new(blockchainMock) + blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headersMap.getHeader) + + testValidatorsCache := &testValidatorsCache{ + validatorsSnapshotCache: newValidatorsSnapshotCache(hclog.NewNullLogger(), newTestState(t), blockchainMock), + } + + snapshot, err := testValidatorsCache.computeSnapshot(nil, 1*epochSize, nil) + assertions.Nil(snapshot) + assertions.ErrorContains(err, "failed to decode extra from the block#10: wrong extra size: 2") +} + +func TestValidatorsSnapshotCache_ComputeSnapshot_ApplyDeltaFail(t *testing.T) { + t.Parallel() + assertions := assert.New(t) + + const ( + totalValidators = 6 + validatorSetSize = totalValidators / 2 + epochSize = uint64(10) + ) + + allValidators := newTestValidators(totalValidators).getPublicIdentities() + headersMap := &testHeadersMap{} + headersMap.addHeader(createValidatorDeltaHeader(t, 0, 0, nil, allValidators[:validatorSetSize])) + headersMap.addHeader(createValidatorDeltaHeader(t, 1*epochSize, 1, nil, allValidators[:validatorSetSize])) + + blockchainMock := new(blockchainMock) + blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(headersMap.getHeader) + + testValidatorsCache := &testValidatorsCache{ + validatorsSnapshotCache: newValidatorsSnapshotCache(hclog.NewNullLogger(), newTestState(t), blockchainMock), + } + + snapshot, err := testValidatorsCache.computeSnapshot(&validatorSnapshot{0, 0, allValidators}, 1*epochSize, nil) + assertions.Nil(snapshot) + assertions.ErrorContains(err, "failed to apply delta to the validators snapshot, block#10") +} + +func createHeaders(t *testing.T, headersMap *testHeadersMap, + fromBlock, toBlock, epoch uint64, oldValidators, newValidators AccountSet) { + t.Helper() + + headersMap.addHeader(createValidatorDeltaHeader(t, fromBlock, epoch-1, oldValidators, newValidators)) + + for i := fromBlock + 1; i <= toBlock; i++ { + headersMap.addHeader(createValidatorDeltaHeader(t, i, epoch, nil, nil)) + } +} + +func createValidatorDeltaHeader(t *testing.T, blockNumber, epoch uint64, oldValidatorSet, newValidatorSet AccountSet) *types.Header { + t.Helper() + + delta, _ := createValidatorSetDelta(oldValidatorSet, newValidatorSet) + extra := &Extra{Validators: delta, Checkpoint: &CheckpointData{EpochNumber: epoch}} + + return &types.Header{ + Number: blockNumber, + ExtraData: append(make([]byte, ExtraVanity), extra.MarshalRLPTo(nil)...), + } +} + +type testValidatorsCache struct { + *validatorsSnapshotCache +} + +func (c *testValidatorsCache) cleanValidatorsCache() error { + c.snapshots = make(map[uint64]*validatorSnapshot) + + return c.state.removeAllValidatorSnapshots() +} diff --git a/consensus/polybft/wallet/account.go b/consensus/polybft/wallet/account.go new file mode 100644 index 0000000000..97fd9a530c --- /dev/null +++ b/consensus/polybft/wallet/account.go @@ -0,0 +1,103 @@ +package wallet + +import ( + "crypto/ecdsa" + "encoding/hex" + "fmt" + + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/secrets" + "github.com/umbracle/ethgo/wallet" +) + +// Account is an account for key signatures +type Account struct { + Ecdsa *wallet.Key + Bls *bls.PrivateKey +} + +// GenerateAccount generates a new random account +func GenerateAccount() *Account { + key, err := wallet.GenerateKey() + if err != nil { + panic("Cannot generate key") + } + + blsKey, err := bls.GenerateBlsKey() + if err != nil { + panic("Cannot generate bls key") + } + + return &Account{ + Ecdsa: key, + Bls: blsKey, + } +} + +// NewAccountFromSecret creates new account by using provided secretsManager +func NewAccountFromSecret(secretsManager secrets.SecretsManager) (*Account, error) { + var ( + encodedKey []byte + err error + ) + + // ECDSA + if encodedKey, err = secretsManager.GetSecret(secrets.ValidatorKey); err != nil { + return nil, fmt.Errorf("failed to read account data: %w", err) + } + + ecdsaRaw, err := hex.DecodeString(string(encodedKey)) + if err != nil { + return nil, err + } + + ecdsaKey, err := wallet.NewWalletFromPrivKey(ecdsaRaw) + if err != nil { + return nil, err + } + + // BLS + if encodedKey, err = secretsManager.GetSecret(secrets.ValidatorBLSKey); err != nil { + return nil, fmt.Errorf("failed to read account data: %w", err) + } + + blsKey, err := bls.UnmarshalPrivateKey(encodedKey) + if err != nil { + return nil, err + } + + return &Account{Ecdsa: ecdsaKey, Bls: blsKey}, nil +} + +// Save persists ECDSA and BLS private keys to the SecretsManager +func (a *Account) Save(secretsManager secrets.SecretsManager) (err error) { + var ( + ecdsaRaw []byte + blsRaw []byte + ) + + // get serialized ecdsa private key + if ecdsaRaw, err = a.Ecdsa.MarshallPrivateKey(); err != nil { + return err + } + + if err = secretsManager.SetSecret(secrets.ValidatorKey, []byte(hex.EncodeToString(ecdsaRaw))); err != nil { + return err + } + + // get serialized bls private key + if blsRaw, err = a.Bls.Marshal(); err != nil { + return err + } + + return secretsManager.SetSecret(secrets.ValidatorBLSKey, blsRaw) +} + +func (a *Account) GetEcdsaPrivateKey() (*ecdsa.PrivateKey, error) { + ecdsaRaw, err := a.Ecdsa.MarshallPrivateKey() + if err != nil { + return nil, err + } + + return wallet.ParsePrivateKey(ecdsaRaw) +} diff --git a/consensus/polybft/wallet/account_test.go b/consensus/polybft/wallet/account_test.go new file mode 100644 index 0000000000..5eab4c2f2a --- /dev/null +++ b/consensus/polybft/wallet/account_test.go @@ -0,0 +1,73 @@ +package wallet + +import ( + "fmt" + "testing" + + "github.com/0xPolygon/polygon-edge/secrets" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAccount(t *testing.T) { + t.Parallel() + + secretsManager := newSecretsManagerMock() + + key := GenerateAccount() + pubKeyMarshalled := key.Bls.PublicKey().Marshal() + privKeyMarshalled, err := key.Bls.Marshal() + require.NoError(t, err) + + require.NoError(t, key.Save(secretsManager)) + + key1, err := NewAccountFromSecret(secretsManager) + require.NoError(t, err) + + pubKeyMarshalled1 := key1.Bls.PublicKey().Marshal() + privKeyMarshalled1, err := key1.Bls.Marshal() + require.NoError(t, err) + + assert.Equal(t, key.Ecdsa.Address(), key1.Ecdsa.Address()) + assert.Equal(t, pubKeyMarshalled, pubKeyMarshalled1) + assert.Equal(t, privKeyMarshalled, privKeyMarshalled1) +} + +func newSecretsManagerMock() secrets.SecretsManager { + return &secretsManagerMock{cache: make(map[string][]byte)} +} + +type secretsManagerMock struct { + cache map[string][]byte +} + +func (sm *secretsManagerMock) Setup() error { + return nil +} + +func (sm *secretsManagerMock) GetSecret(name string) ([]byte, error) { + value, exists := sm.cache[name] + if !exists { + return nil, fmt.Errorf("secret does not exists for %s", name) + } + + return value, nil +} + +func (sm *secretsManagerMock) SetSecret(name string, value []byte) error { + sm.cache[name] = value + + return nil +} + +func (sm *secretsManagerMock) HasSecret(name string) bool { + _, exists := sm.cache[name] + + return exists +} + +func (sm *secretsManagerMock) RemoveSecret(name string) error { + delete(sm.cache, name) + + return nil +} diff --git a/consensus/polybft/wallet/key.go b/consensus/polybft/wallet/key.go new file mode 100644 index 0000000000..a73815cbf9 --- /dev/null +++ b/consensus/polybft/wallet/key.go @@ -0,0 +1,75 @@ +package wallet + +import ( + "fmt" + + "github.com/0xPolygon/go-ibft/messages/proto" + "github.com/0xPolygon/polygon-edge/crypto" + "github.com/0xPolygon/polygon-edge/types" + "github.com/umbracle/ethgo" + protobuf "google.golang.org/protobuf/proto" +) + +type Key struct { + raw *Account +} + +func NewKey(raw *Account) *Key { + return &Key{ + raw: raw, + } +} + +func (k *Key) String() string { + return k.raw.Ecdsa.Address().String() +} + +func (k *Key) Address() ethgo.Address { + return k.raw.Ecdsa.Address() +} + +func (k *Key) Sign(b []byte) ([]byte, error) { + s, err := k.raw.Bls.Sign(b) + if err != nil { + return nil, err + } + + return s.Marshal() +} + +// SignEcdsaMessage signs the proto message with ecdsa +func (k *Key) SignEcdsaMessage(msg *proto.Message) (*proto.Message, error) { + raw, err := protobuf.Marshal(msg) + if err != nil { + return nil, fmt.Errorf("cannot marshal message: %w", err) + } + + if msg.Signature, err = k.raw.Ecdsa.Sign(raw); err != nil { + return nil, fmt.Errorf("cannot create message signature: %w", err) + } + + return msg, nil +} + +// RecoverAddressFromSignature recovers signer address from the given digest and signature +func RecoverAddressFromSignature(sig, msg []byte) (types.Address, error) { + pub, err := crypto.RecoverPubkey(sig, msg) + if err != nil { + return types.Address{}, fmt.Errorf("cannot recover address from signature: %w", err) + } + + return crypto.PubKeyToAddress(pub), nil +} + +// ECDSASigner implements ethgo.Key interface and it is used for signing using provided ECDSA key +type ECDSASigner struct { + *Key +} + +func NewEcdsaSigner(ecdsaKey *Key) *ECDSASigner { + return &ECDSASigner{Key: ecdsaKey} +} + +func (k *ECDSASigner) Sign(b []byte) ([]byte, error) { + return k.raw.Ecdsa.Sign(b) +} diff --git a/consensus/polybft/wallet/key_test.go b/consensus/polybft/wallet/key_test.go new file mode 100644 index 0000000000..f417c39ac7 --- /dev/null +++ b/consensus/polybft/wallet/key_test.go @@ -0,0 +1,54 @@ +package wallet + +import ( + "testing" + + "github.com/0xPolygon/go-ibft/messages/proto" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_RecoverAddressFromSignature(t *testing.T) { + for _, account := range []*Account{GenerateAccount(), GenerateAccount(), GenerateAccount()} { + key := NewKey(account) + msgNoSig := &proto.Message{ + From: key.Address().Bytes(), + Type: proto.MessageType_COMMIT, + Payload: &proto.Message_CommitData{}, + } + + msg, err := key.SignEcdsaMessage(msgNoSig) + require.NoError(t, err) + + payload, err := msgNoSig.PayloadNoSig() + require.NoError(t, err) + + address, err := RecoverAddressFromSignature(msg.Signature, payload) + require.NoError(t, err) + assert.Equal(t, key.Address().Bytes(), address.Bytes()) + } +} + +func Test_Sign(t *testing.T) { + msg := []byte("some message") + + for _, account := range []*Account{GenerateAccount(), GenerateAccount()} { + key := NewKey(account) + ser, err := key.Sign(msg) + + require.NoError(t, err) + + sig, err := bls.UnmarshalSignature(ser) + require.NoError(t, err) + + assert.True(t, sig.Verify(key.raw.Bls.PublicKey(), msg)) + } +} + +func Test_String(t *testing.T) { + for _, account := range []*Account{GenerateAccount(), GenerateAccount(), GenerateAccount()} { + key := NewKey(account) + assert.Equal(t, key.Address().String(), key.String()) + } +} diff --git a/contracts/constants.go b/contracts/constants.go new file mode 100644 index 0000000000..f207ceff11 --- /dev/null +++ b/contracts/constants.go @@ -0,0 +1,8 @@ +package contracts + +const ( + // PolyBFTRegisterMessage is a challenge message which needs to be signed by each validator account + PolyBFTRegisterMessage = "Polybft validator" + // ContractsRootFolder is a relative path of the core smart contracts + ContractsRootFolder = "./core-contracts/artifacts/contracts/" +) diff --git a/contracts/system_addresses.go b/contracts/system_addresses.go new file mode 100644 index 0000000000..4f7848fe5d --- /dev/null +++ b/contracts/system_addresses.go @@ -0,0 +1,27 @@ +package contracts + +import "github.com/0xPolygon/polygon-edge/types" + +var ( + // ValidatorSetContract is an address of validator set contract deployed to child chain + ValidatorSetContract = types.StringToAddress("0x101") + // BLSContract is an address of BLS contract on the child chain + BLSContract = types.StringToAddress("0x102") + // MerkleContract is an address of Merkle contract on the child chain + MerkleContract = types.StringToAddress("0x103") + // StateReceiverContract is an address of bridge contract on the child chain + StateReceiverContract = types.StringToAddress("0x1001") + // NativeTokenContract is an address of bridge contract (used for transferring native tokens on child chain) + NativeTokenContract = types.StringToAddress("0x1010") + // SystemCaller is address of account, used for system calls to smart contracts + SystemCaller = types.StringToAddress("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE") + // L2StateSender is an address of bridge contract to the rootchain + L2StateSenderContract = types.StringToAddress("0x1002") + + // NativeTransferPrecompile is an address of native transfer precompile + NativeTransferPrecompile = types.StringToAddress("0x2020") + // BLSAggSigsVerificationPrecompile is an address of BLS aggregated signatures verificatin precompile + BLSAggSigsVerificationPrecompile = types.StringToAddress("0x2030") + // ConsolePrecompile is and address of Hardhat console precompile + ConsolePrecompile = types.StringToAddress("0x000000000000000000636F6e736F6c652e6c6f67") +) diff --git a/core-contracts b/core-contracts new file mode 160000 index 0000000000..21043c64ba --- /dev/null +++ b/core-contracts @@ -0,0 +1 @@ +Subproject commit 21043c64ba9bf5594f29c5999ca123c15b1593aa diff --git a/crypto/crypto.go b/crypto/crypto.go index 00dab6c48d..71ee9f20d2 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -8,6 +8,7 @@ import ( "crypto/rand" "errors" "fmt" + "hash" "math/big" "github.com/0xPolygon/polygon-edge/helper/hex" @@ -45,6 +46,14 @@ var ( errInvalidSignature = errors.New("invalid signature") ) +// KeccakState wraps sha3.state. In addition to the usual hash methods, it also supports +// Read to get a variable amount of data from the hash state. Read is faster than Sum +// because it doesn't copy the internal state, but also modifies the internal state. +type KeccakState interface { + hash.Hash + Read([]byte) (int, error) +} + func trimLeftZeros(b []byte) []byte { i := 0 for i = range b { @@ -250,6 +259,24 @@ func Keccak256(v ...[]byte) []byte { return h.Sum(nil) } +// Keccak256Hash calculates and returns the Keccak256 hash of the input data, +// converting it to an internal Hash data structure. +func Keccak256Hash(v ...[]byte) (hash types.Hash) { + h := NewKeccakState() + for _, b := range v { + h.Write(b) + } + + h.Read(hash[:]) + + return hash +} + +// NewKeccakState creates a new KeccakState +func NewKeccakState() KeccakState { + return sha3.NewLegacyKeccak256().(KeccakState) //nolint:forcetypeassert +} + // PubKeyToAddress returns the Ethereum address of a public key func PubKeyToAddress(pub *ecdsa.PublicKey) types.Address { buf := Keccak256(MarshalPublicKey(pub)[1:])[12:] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000000..f4a5cba036 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,61 @@ +# Deploying local docker cluster + +## Prerequisites +* [Docker Desktop](https://www.docker.com/products/docker-desktop/) - Docker 17.12.0+ +* [Docker compose 2+](https://github.com/docker/compose/releases/tag/v2.14.1) + +### `polybft` consensus +When deploying with `polybft` consensus, there are some additional dependencies: +* [npm](https://nodejs.org/en/) +* [go 1.18.x](https://go.dev/dl/) + +## Local development +Running `polygon-edge` local cluster with docker can be done very easily by using provided `scripts` folder +or by running `docker-compose` manually. + +### Using provided `scripts` folder +***All commands need to be run from the repo root / root folder.*** + +* `scripts/cluster ibft --docker` - deploy environment with `ibft` consensus +* `scripts/cluster polybft --docker` - deploy environment with `polybft` consensus +* `scripts/cluster {ibft or polybft} --docker stop` - stop containers +* `scripts/cluster {ibft or polybft}--docker destroy` - destroy environment (delete containers and volumes) + +### Using `docker-compose` +***All commands need to be run from the repo root / root folder.*** + +#### use `ibft` PoA consensus +* `export EDGE_CONSENSUS=ibft` - set `ibft` consensus +* `docker-compose -f ./docker/local/docker-compose.yml up -d --build` - deploy environment + +#### use `polybft` consensus +* `cd core-contracts && npm install && npm run compile && cd -` - install `npm` dependencies and compile smart contracts +* `go run ./consensus/polybft/contractsapi/artifacts-gen/main.go` generate needed code +* `export EDGE_CONSENSUS=polybft` - set `polybft` consensus +* `docker-compose -f ./docker/local/docker-compose.yml up -d --build` - deploy environment + +#### stop / destroy +* `docker-compose -f ./docker/local/docker-compose.yml stop` - stop containers +* `docker-compose -f ./docker/local/docker-compose.yml down -v` - destroy environment + +## Customization +Use `docker/local/polygon-edge.sh` script to customize chain parameters. +All parameters can be defined at the very beginning of the script, in the `CHAIN_CUSTOM_OPTIONS` variable. +It already has some default parameters, which can be easily modified. +These are the `genesis` parameters from the official [docs](https://wiki.polygon.technology/docs/edge/get-started/cli-commands#genesis-flags). + +Primarily, the `--premine` parameter needs to be edited to include the accounts that the user has access to. + +## Considerations + +### Submodules +Before deploying `polybft` environment, `core-contracts` submodule needs to be downloaded. +To do that simply run `make download-submodules`. + +### Build times +When building containers for the first time (or after purging docker build cache) +it might take a while to complete, depending on the hardware that the build operation is running on. + +### Production +This is **NOT** a production ready deployment. It is to be used in *development* / *test* environments only. +For production usage, please check out the official [docs](https://wiki.polygon.technology/docs/edge/overview/). diff --git a/docker/local/Dockerfile b/docker/local/Dockerfile index 308ecd5f1e..afffcbc864 100644 --- a/docker/local/Dockerfile +++ b/docker/local/Dockerfile @@ -17,6 +17,7 @@ WORKDIR /polygon-edge COPY --from=builder /polygon-edge/polygon-edge ./ COPY ./docker/local/polygon-edge.sh ./ +COPY ./core-contracts/artifacts ./core-contracts/artifacts # Expose json-rpc, libp2p and grpc ports EXPOSE 8545 9632 1478 5001 diff --git a/docker/local/docker-compose.yml b/docker/local/docker-compose.yml index 39f0e81682..425b95bd36 100644 --- a/docker/local/docker-compose.yml +++ b/docker/local/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.5' +version: '3.9' services: ## INITIALIZE GENESIS AND SECRETS @@ -6,18 +6,18 @@ services: build: context: ../../ dockerfile: docker/local/Dockerfile - command: ["init"] + image: local/polygon-edge + container_name: polygon-edge-bootstrapper + command: [ "init", "${EDGE_CONSENSUS:-ibft}" ] volumes: - data:/data networks: - polygon-edge-docker - + ## RUN NODES - ## Nodes must have the same names as folders in genesis-legde node-1: - build: - context: ../../ - dockerfile: docker/local/Dockerfile + image: local/polygon-edge + container_name: polygon-edge-validator-1 command: ["server", "--data-dir", "/data/data-1", "--chain", "/data/genesis.json", "--grpc-address", "0.0.0.0:9632", "--libp2p", "0.0.0.0:1478", "--jsonrpc", "0.0.0.0:8545", "--prometheus", "0.0.0.0:5001", "--seal"] depends_on: init: @@ -31,11 +31,10 @@ services: networks: - polygon-edge-docker restart: on-failure - + node-2: - build: - context: ../../ - dockerfile: docker/local/Dockerfile + image: local/polygon-edge + container_name: polygon-edge-validator-2 command: ["server", "--data-dir", "/data/data-2", "--chain", "/data/genesis.json", "--grpc-address", "0.0.0.0:9632", "--libp2p", "0.0.0.0:1478", "--jsonrpc", "0.0.0.0:8545", "--prometheus", "0.0.0.0:5001", "--seal"] depends_on: init: @@ -49,11 +48,10 @@ services: networks: - polygon-edge-docker restart: on-failure - + node-3: - build: - context: ../../ - dockerfile: docker/local/Dockerfile + image: local/polygon-edge + container_name: polygon-edge-validator-3 command: ["server", "--data-dir", "/data/data-3", "--chain", "/data/genesis.json", "--grpc-address", "0.0.0.0:9632", "--libp2p", "0.0.0.0:1478", "--jsonrpc", "0.0.0.0:8545", "--prometheus", "0.0.0.0:5001", "--seal"] depends_on: init: @@ -67,11 +65,10 @@ services: networks: - polygon-edge-docker restart: on-failure - + node-4: - build: - context: ../../ - dockerfile: docker/local/Dockerfile + image: local/polygon-edge + container_name: polygon-edge-validator-4 command: ["server", "--data-dir", "/data/data-4", "--chain", "/data/genesis.json", "--grpc-address", "0.0.0.0:9632", "--libp2p", "0.0.0.0:1478", "--jsonrpc", "0.0.0.0:8545", "--prometheus", "0.0.0.0:5001", "--seal"] depends_on: init: diff --git a/docker/local/polygon-edge.sh b/docker/local/polygon-edge.sh index d102d56d1b..d2296ec09b 100755 --- a/docker/local/polygon-edge.sh +++ b/docker/local/polygon-edge.sh @@ -2,36 +2,58 @@ set -e -POLYGON_EDGE_BIN=/polygon-edge/polygon-edge -GENESIS_PATH=/data/genesis.json +POLYGON_EDGE_BIN=./polygon-edge +CHAIN_CUSTOM_OPTIONS=$(tr "\n" " " << EOL +--block-gas-limit 10000000 +--epoch-size 10 +--chain-id 51001 +--name polygon-edge-docker +--premine 0x228466F2C715CbEC05dEAbfAc040ce3619d7CF0B:0xD3C21BCECCEDA1000000 +--premine 0xca48694ebcB2548dF5030372BE4dAad694ef174e:0xD3C21BCECCEDA1000000 +EOL +) case "$1" in "init") - if [ -f "$GENESIS_PATH" ]; then - echo "Secrets have already been generated." - else - echo "Generating secrets..." - secrets=$("$POLYGON_EDGE_BIN" secrets init --num 4 --data-dir /data/data- --insecure --json) - echo "Secrets have been successfully generated" - - echo "Generating genesis file..." - cd /data && "$POLYGON_EDGE_BIN" genesis \ - --dir "$GENESIS_PATH" \ - --consensus ibft \ - --ibft-validators-prefix-path data- \ - --bootnode /dns4/node-1/tcp/1478/p2p/$(echo $secrets | jq -r '.[0] | .node_id') \ - --bootnode /dns4/node-2/tcp/1478/p2p/$(echo $secrets | jq -r '.[1] | .node_id') - echo "Genesis file has been successfully generated" - fi + case "$2" in + "ibft") + if [ -f "$GENESIS_PATH" ]; then + echo "Secrets have already been generated." + else + echo "Generating secrets..." + secrets=$("$POLYGON_EDGE_BIN" secrets init --insecure --num 4 --data-dir /data/data- --json) + echo "Secrets have been successfully generated" + echo "Generating IBFT Genesis file..." + cd /data && /polygon-edge/polygon-edge genesis $CHAIN_CUSTOM_OPTIONS \ + --dir genesis.json \ + --consensus ibft \ + --ibft-validators-prefix-path data- \ + --validator-set-size=4 \ + --bootnode /dns4/node-1/tcp/1478/p2p/$(echo $secrets | jq -r '.[0] | .node_id') \ + --bootnode /dns4/node-2/tcp/1478/p2p/$(echo $secrets | jq -r '.[1] | .node_id') \ + ;; + "polybft") + echo "Generating PolyBFT secrets..." + secrets=$("$POLYGON_EDGE_BIN" polybft-secrets init --insecure --num 4 --data-dir /data/data- --json) + echo "Secrets have been successfully generated" + + echo "Generating manifest..." + "$POLYGON_EDGE_BIN" manifest --path /data/manifest.json --validators-path /data --validators-prefix data- + + echo "Generating PolyBFT Genesis file..." + "$POLYGON_EDGE_BIN" genesis $CHAIN_CUSTOM_OPTIONS \ + --dir /data/genesis.json \ + --consensus polybft \ + --manifest /data/manifest.json \ + --validator-set-size=4 \ + --bootnode /dns4/node-1/tcp/1478/p2p/$(echo $secrets | jq -r '.[0] | .node_id') \ + --bootnode /dns4/node-2/tcp/1478/p2p/$(echo $secrets | jq -r '.[1] | .node_id') + ;; + esac ;; *) - until [ -f "$GENESIS_PATH" ] - do - echo "Waiting 1s for genesis file $GENESIS_PATH to be created by init container..." - sleep 1 - done echo "Executing polygon-edge..." exec "$POLYGON_EDGE_BIN" "$@" ;; diff --git a/e2e-polybft/README.md b/e2e-polybft/README.md new file mode 100644 index 0000000000..a74ceefa23 --- /dev/null +++ b/e2e-polybft/README.md @@ -0,0 +1,22 @@ + +# End-to-End testing + +The implemented E2E tests start a local instance of Polybft consensus protocol. + +As such, they require the binary 'polygon-edge' to be available in the $PATH variable. + +## Step 1: Build the polygon-edge + +```bash +go build -race -o artifacts/polygon-edge . && mv artifacts/polygon-edge $GOPATH/bin +``` + +In this case we expect that $GOPATH is set and $GOPATH/bin is defined in $PATH as it is required for a complete Go installation. + +## Step 2: Run the tests + +```bash +export E2E_TESTS=TRUE && go test -v ./e2e-polybft/... +``` + +To enable logs in the e2e test set `E2E_LOGS=true`. diff --git a/e2e-polybft/bridge_test.go b/e2e-polybft/bridge_test.go new file mode 100644 index 0000000000..e645f9c581 --- /dev/null +++ b/e2e-polybft/bridge_test.go @@ -0,0 +1,676 @@ +package e2e + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "math/big" + "net/http" + "path" + "strconv" + "strings" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/command/genesis" + rootchainHelper "github.com/0xPolygon/polygon-edge/command/rootchain/helper" + "github.com/0xPolygon/polygon-edge/command/sidechain" + "github.com/0xPolygon/polygon-edge/consensus/polybft" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi/artifact" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/e2e-polybft/framework" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo" + ethgow "github.com/umbracle/ethgo/wallet" +) + +const ( + manifestFileName = "manifest.json" +) + +// checkLogs is helper function which parses given ResultEvent event's logs, +// extracts status topic value and makes assertions against it. +func checkLogs( + t *testing.T, + logs []*ethgo.Log, + expectedCount int, +) { + t.Helper() + require.Len(t, logs, expectedCount) + + for _, log := range logs { + stateSyncResultEvent := &contractsapi.StateSyncResultEvent{} + assert.NoError(t, stateSyncResultEvent.ParseLog(log)) + + t.Logf("Block Number=%d, Decoded Log=%+v", log.BlockNumber, stateSyncResultEvent) + + assert.True(t, stateSyncResultEvent.Status) + } +} + +func TestE2E_Bridge_MainWorkflow(t *testing.T) { + const num = 10 + + var ( + accounts = make([]ethgo.Key, num) + wallets, amounts [num]string + premine [num]types.Address + ) + + for i := 0; i < num; i++ { + accounts[i], _ = ethgow.GenerateKey() + premine[i] = types.Address(accounts[i].Address()) + wallets[i] = premine[i].String() + amounts[i] = fmt.Sprintf("%d", 100) + } + + cluster := framework.NewTestCluster(t, 5, framework.WithBridge(), framework.WithPremine(premine[:]...)) + defer cluster.Stop() + + // wait for a couple of blocks + require.NoError(t, cluster.WaitForBlock(2, 1*time.Minute)) + + // send a few transactions to the bridge + require.NoError( + t, + cluster.EmitTransfer( + contracts.NativeTokenContract.String(), + strings.Join(wallets[:], ","), + strings.Join(amounts[:], ","), + ), + ) + + // wait for a few more sprints + require.NoError(t, cluster.WaitForBlock(35, 2*time.Minute)) + + // the transactions are mined and there should be a success events + id := contractsapi.StateReceiver.Abi.Events["StateSyncResult"].ID() + filter := ðgo.LogFilter{ + Topics: [][]*ethgo.Hash{ + {&id}, + }, + } + + filter.SetFromUint64(0) + filter.SetToUint64(100) + + logs, err := cluster.Servers[0].JSONRPC().Eth().GetLogs(filter) + require.NoError(t, err) + + // Assert that all state syncs are executed successfully + checkLogs(t, logs, num) +} + +func TestE2E_Bridge_MultipleCommitmentsPerEpoch(t *testing.T) { + const num = 10 + + var ( + accounts = make([]ethgo.Key, num) + wallets, amounts [num]string + premine [num]types.Address + ) + + for i := 0; i < num; i++ { + accounts[i], _ = ethgow.GenerateKey() + premine[i] = types.Address(accounts[i].Address()) + wallets[i] = premine[i].String() + amounts[i] = fmt.Sprintf("%d", 100) + } + + cluster := framework.NewTestCluster(t, 5, framework.WithBridge(), framework.WithPremine(premine[:]...), framework.WithEpochSize(30)) + defer cluster.Stop() + + // wait for a couple of blocks + require.NoError(t, cluster.WaitForBlock(2, 1*time.Minute)) + + // send two transactions to the bridge so that we have a minimal commitment + require.NoError( + t, + cluster.EmitTransfer( + contracts.NativeTokenContract.String(), + strings.Join(wallets[:2], ","), + strings.Join(amounts[:2], ","), + ), + ) + + // wait for a few more sprints + require.NoError(t, cluster.WaitForBlock(10, 2*time.Minute)) + + client := cluster.Servers[0].JSONRPC() + txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithClient(client)) + require.NoError(t, err) + + lastCommittedIDMethod := contractsapi.StateReceiver.Abi.GetMethod("lastCommittedId") + encode, err := lastCommittedIDMethod.Encode([]interface{}{}) + require.NoError(t, err) + + // check that we submitted the minimal commitment to smart contract + result, err := txRelayer.Call(accounts[0].Address(), ethgo.Address(contracts.StateReceiverContract), encode) + require.NoError(t, err) + + lastCommittedID, err := strconv.ParseUint(result, 0, 64) + require.NoError(t, err) + require.Equal(t, uint64(2), lastCommittedID) + + // send some more transactions to the bridge to build another commitment in epoch + require.NoError( + t, + cluster.EmitTransfer( + contracts.NativeTokenContract.String(), + strings.Join(wallets[2:], ","), + strings.Join(amounts[2:], ","), + ), + ) + + // wait for a few more sprints + require.NoError(t, cluster.WaitForBlock(40, 3*time.Minute)) + + // check that we submitted the minimal commitment to smart contract + result, err = txRelayer.Call(accounts[0].Address(), ethgo.Address(contracts.StateReceiverContract), encode) + require.NoError(t, err) + + // check that the second (larger commitment) was also submitted in epoch + lastCommittedID, err = strconv.ParseUint(result, 0, 64) + require.NoError(t, err) + require.Equal(t, uint64(10), lastCommittedID) + + // the transactions are mined and state syncs should be executed by the relayer + // and there should be a success events + id := contractsapi.StateReceiver.Abi.Events["StateSyncResult"].ID() + filter := ðgo.LogFilter{ + Topics: [][]*ethgo.Hash{ + {&id}, + }, + } + + filter.SetFromUint64(0) + filter.SetToUint64(100) + + logs, err := cluster.Servers[0].JSONRPC().Eth().GetLogs(filter) + require.NoError(t, err) + + // Assert that all state syncs are executed successfully + checkLogs(t, logs, num) +} + +func TestE2E_CheckpointSubmission(t *testing.T) { + // spin up a cluster with epoch size set to 5 blocks + cluster := framework.NewTestCluster(t, 5, framework.WithBridge(), framework.WithEpochSize(5)) + defer cluster.Stop() + + // initialize tx relayer used to query CheckpointManager smart contract + l1Relayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(cluster.Bridge.JSONRPCAddr())) + require.NoError(t, err) + + manifest, err := polybft.LoadManifest(path.Join(cluster.Config.TmpDir, manifestFileName)) + require.NoError(t, err) + + checkpointManagerAddr := ethgo.Address(manifest.RootchainConfig.CheckpointManagerAddress) + rootchainSender := ethgo.Address(manifest.RootchainConfig.AdminAddress) + + testCheckpointBlockNumber := func(expectedCheckpointBlock uint64) (bool, error) { + actualCheckpointBlock, err := getCheckpointBlockNumber(l1Relayer, checkpointManagerAddr, rootchainSender) + if err != nil { + return false, err + } + + t.Logf("Checkpoint block: %d\n", actualCheckpointBlock) + + return actualCheckpointBlock == expectedCheckpointBlock, nil + } + + // wait for a single epoch to be checkpointed + require.NoError(t, cluster.WaitForBlock(7, 30*time.Second)) + + // checking last checkpoint block before rootchain server stop + err = cluster.Bridge.WaitUntil(2*time.Second, 30*time.Second, func() (bool, error) { + return testCheckpointBlockNumber(5) + }) + require.NoError(t, err) + + // stop rootchain server + cluster.Bridge.Stop() + + // wait for a couple of epochs so that there are pending checkpoint (epoch-ending) blocks + require.NoError(t, cluster.WaitForBlock(21, 2*time.Minute)) + + // restart rootchain server + cluster.Bridge.Start() + + // check if pending checkpoint blocks were submitted (namely the last checkpointed block must be block 20) + err = cluster.Bridge.WaitUntil(2*time.Second, 50*time.Second, func() (bool, error) { + return testCheckpointBlockNumber(20) + }) + require.NoError(t, err) +} + +// getCheckpointBlockNumber gets current checkpoint block number from checkpoint manager smart contract +func getCheckpointBlockNumber(l1Relayer txrelayer.TxRelayer, checkpointManagerAddr, sender ethgo.Address) (uint64, error) { + checkpointBlockNumRaw, err := ABICall(l1Relayer, contractsapi.CheckpointManager, + checkpointManagerAddr, sender, "currentCheckpointBlockNumber") + if err != nil { + return 0, err + } + + actualCheckpointBlock, err := types.ParseUint64orHex(&checkpointBlockNumRaw) + if err != nil { + return 0, err + } + + return actualCheckpointBlock, nil +} + +func TestE2E_Bridge_L2toL1Exit(t *testing.T) { + const ( + userNumber = 10 + epochSize = 30 + checkpointBlock = uint64(epochSize) + checkpointEpoch = uint64(1) + ) + + sidechainKeys := make([]*ethgow.Key, userNumber) + accountAddress := make([]types.Address, userNumber) + + for i := 0; i < userNumber; i++ { + key, err := ethgow.GenerateKey() + require.NoError(t, err) + + sidechainKeys[i] = key + accountAddress[i] = types.Address(key.Address()) + } + + // initialize rootchain admin key to default one + require.NoError(t, rootchainHelper.InitRootchainAdminKey("")) + + cluster := framework.NewTestCluster(t, 5, + framework.WithBridge(), + framework.WithPremine(accountAddress...), + framework.WithEpochSize(epochSize), + ) + + defer cluster.Stop() + + manifest, err := polybft.LoadManifest(path.Join(cluster.Config.TmpDir, manifestFileName)) + require.NoError(t, err) + + checkpointManagerAddr := ethgo.Address(manifest.RootchainConfig.CheckpointManagerAddress) + exitHelperAddr := ethgo.Address(manifest.RootchainConfig.ExitHelperAddress) + adminAddr := ethgo.Address(manifest.RootchainConfig.AdminAddress) + + // wait for a couple of blocks + require.NoError(t, cluster.WaitForBlock(2, 2*time.Minute)) + + // init rpc clients + l1TxRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(txrelayer.DefaultRPCAddress)) + require.NoError(t, err) + l2TxRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(cluster.Servers[0].JSONRPCAddr())) + require.NoError(t, err) + + // deploy L1ExitTest contract + receipt, err := l1TxRelayer.SendTransaction(ðgo.Transaction{Input: contractsapi.TestL1StateReceiver.Bytecode}, + rootchainHelper.GetRootchainAdminKey()) + require.NoError(t, err) + require.Equal(t, receipt.Status, uint64(types.ReceiptSuccess)) + + l1ExitTestAddr := receipt.ContractAddress + l2StateSenderAddress := ethgo.Address(contracts.L2StateSenderContract) + + // Start test + // send crosschain transaction on l2 and get exit id + stateSenderData := []byte{123} + for i := 0; i < userNumber; i++ { + receipt, err := ABITransaction(l2TxRelayer, sidechainKeys[i], contractsapi.L2StateSender, l2StateSenderAddress, "syncState", l1ExitTestAddr, stateSenderData) + require.NoError(t, err) + require.Equal(t, receipt.Status, uint64(types.ReceiptSuccess)) + } + + require.NoError(t, cluster.WaitForBlock(epochSize, 2*time.Minute)) + + fail := 0 + + for range time.Tick(time.Second) { + currentEpochString, err := ABICall(l1TxRelayer, contractsapi.CheckpointManager, checkpointManagerAddr, adminAddr, "currentEpoch") + require.NoError(t, err) + + currentEpoch, err := types.ParseUint64orHex(¤tEpochString) + require.NoError(t, err) + + if currentEpoch >= checkpointEpoch { + break + } + + if fail > 300 { + t.Fatal("epoch havent achieved") + } + fail++ + } + + var proof types.Proof + + for i := 0; i < userNumber; i++ { + exitID := uint64(i + 1) // because exit events start from ID = 1 + proof, err = getExitProof(cluster.Servers[0].JSONRPCAddr(), exitID, checkpointEpoch, checkpointBlock) + require.NoError(t, err) + + isProcessed, err := isExitEventProcessed(sidechainKeys[i], proof, checkpointBlock, stateSenderData, l1ExitTestAddr, exitHelperAddr, adminAddr, l1TxRelayer, exitID) + require.NoError(t, err) + require.True(t, isProcessed) + } +} + +func TestE2E_Bridge_L2toL1ExitMultiple(t *testing.T) { + const ( + userNumber = 6 + epochSize = 10 + roundNumber = 3 + checkpointBlock = uint64(epochSize) + checkpointEpoch = uint64(1) + ) + + exitEventIds := make([]uint64, userNumber*roundNumber) + + sidechainKeys := make([]*ethgow.Key, userNumber) + accountAddress := make([]types.Address, userNumber) + + for i := 0; i < userNumber; i++ { + key, err := ethgow.GenerateKey() + require.NoError(t, err) + + sidechainKeys[i] = key + accountAddress[i] = types.Address(key.Address()) + } + + // initialize rootchain admin key to default one + require.NoError(t, rootchainHelper.InitRootchainAdminKey("")) + + cluster := framework.NewTestCluster(t, 5, + framework.WithBridge(), + framework.WithPremine(accountAddress...), + framework.WithEpochSize(epochSize), + ) + + defer cluster.Stop() + + manifest, err := polybft.LoadManifest(path.Join(cluster.Config.TmpDir, manifestFileName)) + require.NoError(t, err) + + checkpointManagerAddr := ethgo.Address(manifest.RootchainConfig.CheckpointManagerAddress) + exitHelperAddr := ethgo.Address(manifest.RootchainConfig.ExitHelperAddress) + adminAddr := ethgo.Address(manifest.RootchainConfig.AdminAddress) + + // wait for a couple of blocks + require.NoError(t, cluster.WaitForBlock(2, 2*time.Minute)) + + // init rpc clients + l1TxRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(txrelayer.DefaultRPCAddress)) + require.NoError(t, err) + l2TxRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(cluster.Servers[0].JSONRPCAddr())) + require.NoError(t, err) + + // deploy L1ExitTest contract + receipt, err := l1TxRelayer.SendTransaction(ðgo.Transaction{Input: contractsapi.TestL1StateReceiver.Bytecode}, + rootchainHelper.GetRootchainAdminKey()) + require.NoError(t, err) + require.Equal(t, receipt.Status, uint64(types.ReceiptSuccess)) + + l1ExitTestAddr := receipt.ContractAddress + l2StateSenderAddress := ethgo.Address(contracts.L2StateSenderContract) + + // Start test + // send crosschain transaction on l2 and get exit id + stateSenderData := []byte{123} + //g, _ := errgroup.WithContext(context.Background()) + addTransaction := func(j, i uint64) { + receipt, err := ABITransaction(l2TxRelayer, sidechainKeys[j], contractsapi.L2StateSender, l2StateSenderAddress, "syncState", l1ExitTestAddr, stateSenderData) + require.NoError(t, err) + require.Equal(t, receipt.Status, uint64(types.ReceiptSuccess)) + + eventData, err := contractsapi.L2StateSender.Abi.Events["L2StateSynced"].ParseLog(receipt.Logs[0]) + require.NoError(t, err) + + exitEventIds[j+(i-1)*userNumber] = eventData["id"].(*big.Int).Uint64() //nolint:forcetypeassert + } + + for i := 1; i <= roundNumber; i++ { + for j := 0; j < userNumber; j++ { + go addTransaction(uint64(j), uint64(i)) + } + require.NoError(t, cluster.WaitForBlock(uint64((i)*epochSize), 2*time.Minute)) + + fail := 0 + + for range time.Tick(time.Second) { + currentEpochString, err := ABICall(l1TxRelayer, contractsapi.CheckpointManager, checkpointManagerAddr, adminAddr, "currentEpoch") + require.NoError(t, err) + + currentEpoch, err := types.ParseUint64orHex(¤tEpochString) + require.NoError(t, err) + + if currentEpoch >= uint64(i)*checkpointEpoch { + break + } + + if fail > 300 { + t.Fatal("epoch havent achieved") + } + fail++ + } + } + + var proof types.Proof + + for i := 0; i < roundNumber; i++ { + for j := 0; j < userNumber; j++ { + proof, err = getExitProof(cluster.Servers[0].JSONRPCAddr(), exitEventIds[j+i*userNumber], uint64(i+1)*checkpointEpoch, uint64(i+1)*checkpointBlock) + require.NoError(t, err) + isProcessed, err := isExitEventProcessed(sidechainKeys[j], proof, uint64(i+1)*checkpointBlock, stateSenderData, l1ExitTestAddr, exitHelperAddr, adminAddr, l1TxRelayer, exitEventIds[j+i*userNumber]) + require.NoError(t, err) + require.True(t, isProcessed) + } + } +} + +func isExitEventProcessed(sidechainKey *ethgow.Key, proof types.Proof, checkpointBlock uint64, stateSenderData []byte, l1ExitTestAddr, exitHelperAddr, adminAddr ethgo.Address, l1TxRelayer txrelayer.TxRelayer, exitEventID uint64) (bool, error) { + proofExitEventEncoded, err := polybft.ExitEventABIType.Encode(&polybft.ExitEvent{ + ID: exitEventID, + Sender: sidechainKey.Address(), + Receiver: l1ExitTestAddr, + Data: stateSenderData, + }) + if err != nil { + return false, err + } + + leafIndex, ok := proof.Metadata["LeafIndex"].(float64) + if !ok { + return false, fmt.Errorf("could not get leaf index from exit event proof. Leaf from proof: %v", proof.Metadata["LeafIndex"]) + } + + receipt, err := ABITransaction(l1TxRelayer, rootchainHelper.GetRootchainAdminKey(), contractsapi.ExitHelper, exitHelperAddr, + "exit", + big.NewInt(int64(checkpointBlock)), + uint64(leafIndex), + proofExitEventEncoded, + proof.Data, + ) + + if err != nil { + return false, err + } + + if receipt.Status != uint64(types.ReceiptSuccess) { + return false, errors.New("transaction execution failed") + } + + result, err := ABICall(l1TxRelayer, contractsapi.ExitHelper, exitHelperAddr, adminAddr, "processedExits", big.NewInt(int64(exitEventID))) + if err != nil { + return false, err + } + + parserRes, err := types.ParseUint64orHex(&result) + if err != nil { + return false, err + } + + return parserRes == uint64(1), nil +} + +func getExitProof(rpcAddress string, exitID, epoch, checkpointBlock uint64) (types.Proof, error) { + query := struct { + Jsonrpc string `json:"jsonrpc"` + Method string `json:"method"` + Params []string `json:"params"` + ID int `json:"id"` + }{ + "2.0", + "bridge_generateExitProof", + []string{fmt.Sprintf("0x%x", exitID), fmt.Sprintf("0x%x", epoch), fmt.Sprintf("0x%x", checkpointBlock)}, + 1, + } + + d, err := json.Marshal(query) + if err != nil { + return types.Proof{}, err + } + + resp, err := http.Post(rpcAddress, "application/json", bytes.NewReader(d)) + if err != nil { + return types.Proof{}, err + } + + s, err := io.ReadAll(resp.Body) + if err != nil { + return types.Proof{}, err + } + + rspProof := struct { + Result types.Proof `json:"result"` + }{} + + err = json.Unmarshal(s, &rspProof) + if err != nil { + return types.Proof{}, err + } + + return rspProof.Result, nil +} + +// TODO: Remove this to some separate file, containing helper functions? +func ABICall(relayer txrelayer.TxRelayer, artifact *artifact.Artifact, contractAddress ethgo.Address, senderAddr ethgo.Address, method string, params ...interface{}) (string, error) { + input, err := artifact.Abi.GetMethod(method).Encode(params) + if err != nil { + return "", err + } + + return relayer.Call(senderAddr, contractAddress, input) +} + +func ABITransaction(relayer txrelayer.TxRelayer, key ethgo.Key, artifact *artifact.Artifact, contractAddress ethgo.Address, method string, params ...interface{}) (*ethgo.Receipt, error) { + input, err := artifact.Abi.GetMethod(method).Encode(params) + if err != nil { + return nil, err + } + + return relayer.SendTransaction(ðgo.Transaction{ + To: &contractAddress, + Input: input, + }, key) +} + +func TestE2E_Bridge_ChangeVotingPower(t *testing.T) { + const ( + finalBlockNumber = 20 + votingPowerChanges = 3 + ) + + cluster := framework.NewTestCluster(t, 5, + framework.WithBridge(), + framework.WithEpochSize(5), + framework.WithEpochReward(1000)) + defer cluster.Stop() + + // load manifest file + manifest, err := polybft.LoadManifest(path.Join(cluster.Config.TmpDir, manifestFileName)) + require.NoError(t, err) + + checkpointManagerAddr := ethgo.Address(manifest.RootchainConfig.CheckpointManagerAddress) + + validatorSecretFiles, err := genesis.GetValidatorKeyFiles(cluster.Config.TmpDir, cluster.Config.ValidatorPrefix) + require.NoError(t, err) + + votingPowerChangeValidators := make([]ethgo.Address, votingPowerChanges) + + for i := 0; i < votingPowerChanges; i++ { + validator, err := sidechain.GetAccountFromDir(path.Join(cluster.Config.TmpDir, validatorSecretFiles[i])) + require.NoError(t, err) + + votingPowerChangeValidators[i] = validator.Ecdsa.Address() + } + + // L2 Tx relayer (for sending stake transaction and querying validator) + l2Relayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(cluster.Servers[0].JSONRPCAddr())) + require.NoError(t, err) + + // L1 Tx relayer (for querying checkpoints) + l1Relayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(cluster.Bridge.JSONRPCAddr())) + require.NoError(t, err) + + // waiting two epochs, so that some rewards get accumulated + require.NoError(t, cluster.WaitForBlock(10, 1*time.Minute)) + + queryValidators := func(handler func(idx int, validatorInfo *polybft.ValidatorInfo)) { + for i, validatorAddr := range votingPowerChangeValidators { + // query validator info + validatorInfo, err := sidechain.GetValidatorInfo(validatorAddr, l2Relayer) + require.NoError(t, err) + + handler(i, validatorInfo) + } + } + + originalValidatorStorage := make(map[ethgo.Address]*polybft.ValidatorInfo, votingPowerChanges) + + queryValidators(func(idx int, validator *polybft.ValidatorInfo) { + t.Logf("[Validator#%d] Voting power (original)=%d, rewards=%d\n", + idx+1, validator.TotalStake, validator.WithdrawableRewards) + + originalValidatorStorage[validator.Address] = validator + + // stake rewards + require.NoError(t, cluster.Servers[idx].Stake(validator.WithdrawableRewards.Uint64())) + }) + + // wait a two more epochs, so that stake is registered and two more checkpoints are sent. + // Blocks are still produced, although voting power is slightly changed. + require.NoError(t, cluster.WaitForBlock(finalBlockNumber, 1*time.Minute)) + + queryValidators(func(idx int, validator *polybft.ValidatorInfo) { + t.Logf("[Validator#%d] Voting power (after stake)=%d\n", idx+1, validator.TotalStake) + + previousValidatorInfo := originalValidatorStorage[validator.Address] + stakedAmount := new(big.Int).Add(previousValidatorInfo.WithdrawableRewards, previousValidatorInfo.TotalStake) + + // assert that total stake has increased by staked amount + require.Equal(t, stakedAmount, validator.TotalStake) + }) + + l1Sender := ethgo.Address(manifest.RootchainConfig.AdminAddress) + // assert that block 20 gets checkpointed + require.NoError(t, cluster.Bridge.WaitUntil(time.Second, time.Minute, func() (bool, error) { + actualCheckpointBlock, err := getCheckpointBlockNumber(l1Relayer, checkpointManagerAddr, l1Sender) + if err != nil { + return false, err + } + + t.Logf("Checkpoint block: %d\n", actualCheckpointBlock) + + return actualCheckpointBlock == finalBlockNumber, nil + })) +} diff --git a/e2e-polybft/consensus_test.go b/e2e-polybft/consensus_test.go new file mode 100644 index 0000000000..d583a09f90 --- /dev/null +++ b/e2e-polybft/consensus_test.go @@ -0,0 +1,403 @@ +package e2e + +import ( + "math/big" + "path" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/command/genesis" + "github.com/0xPolygon/polygon-edge/command/sidechain" + "github.com/0xPolygon/polygon-edge/consensus/polybft" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/e2e-polybft/framework" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo" +) + +func TestE2E_Consensus_Basic_WithNonValidators(t *testing.T) { + cluster := framework.NewTestCluster(t, 7, + framework.WithNonValidators(2), framework.WithValidatorSnapshot(5)) + defer cluster.Stop() + + require.NoError(t, cluster.WaitForBlock(22, 1*time.Minute)) +} + +func TestE2E_Consensus_Sync_WithNonValidators(t *testing.T) { + // one non-validator node from the ensemble gets disconnected and connected again. + // It should be able to pick up from the synchronization protocol again. + cluster := framework.NewTestCluster(t, 7, + framework.WithNonValidators(2), framework.WithValidatorSnapshot(5)) + defer cluster.Stop() + + // wait for the start + require.NoError(t, cluster.WaitForBlock(20, 1*time.Minute)) + + // stop one non-validator node + node := cluster.Servers[6] + node.Stop() + + // wait for at least 15 more blocks before starting again + require.NoError(t, cluster.WaitForBlock(35, 2*time.Minute)) + + // start the node again + node.Start() + + // wait for block 55 + require.NoError(t, cluster.WaitForBlock(55, 2*time.Minute)) +} + +func TestE2E_Consensus_Sync(t *testing.T) { + // one node from the ensemble gets disconnected and connected again. + // It should be able to pick up from the synchronization protocol again. + cluster := framework.NewTestCluster(t, 6, framework.WithValidatorSnapshot(6)) + defer cluster.Stop() + + // wait for the start + require.NoError(t, cluster.WaitForBlock(5, 1*time.Minute)) + + // stop one node + node := cluster.Servers[0] + node.Stop() + + // wait for at least 15 more blocks before starting again + require.NoError(t, cluster.WaitForBlock(20, 2*time.Minute)) + + // start the node again + node.Start() + + // wait for block 35 + require.NoError(t, cluster.WaitForBlock(35, 2*time.Minute)) +} + +func TestE2E_Consensus_Bulk_Drop(t *testing.T) { + clusterSize := 5 + bulkToDrop := 3 + + cluster := framework.NewTestCluster(t, clusterSize) + defer cluster.Stop() + + // wait for cluster to start + require.NoError(t, cluster.WaitForBlock(5, 1*time.Minute)) + + // drop bulk of nodes from cluster + for i := 0; i < bulkToDrop; i++ { + node := cluster.Servers[i] + node.Stop() + } + + // start dropped nodes again + for i := 0; i < bulkToDrop; i++ { + node := cluster.Servers[i] + node.Start() + } + + // wait for block 10 + require.NoError(t, cluster.WaitForBlock(10, 2*time.Minute)) +} + +func TestE2E_Consensus_RegisterValidator(t *testing.T) { + const ( + validatorSize = 5 + newValidatorSecrets = "test-chain-6" + premineBalance = "0x1A784379D99DB42000000" // 2M native tokens (so that we have enough balance to fund new validator) + ) + + newValidatorStakeRaw := "0x152D02C7E14AF6800000" // 100k native tokens + newValidatorBalanceRaw := "0xD3C21BCECCEDA1000000" // 1M native tokens + newValidatorStake, err := types.ParseUint256orHex(&newValidatorStakeRaw) + require.NoError(t, err) + + cluster := framework.NewTestCluster(t, validatorSize, + framework.WithEpochSize(5), + framework.WithEpochReward(1000), + framework.WithPremineValidators(premineBalance)) + defer cluster.Stop() + srv := cluster.Servers[0] + txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(srv.JSONRPCAddr())) + require.NoError(t, err) + + systemState := polybft.NewSystemState( + &polybft.PolyBFTConfig{ + StateReceiverAddr: contracts.StateReceiverContract, + ValidatorSetAddr: contracts.ValidatorSetContract}, + &e2eStateProvider{txRelayer: txRelayer}) + + // create new account + _, err = cluster.InitSecrets(newValidatorSecrets, 1) + require.NoError(t, err) + + // assert that account is created + validatorSecrets, err := genesis.GetValidatorKeyFiles(cluster.Config.TmpDir, cluster.Config.ValidatorPrefix) + require.NoError(t, err) + require.Equal(t, validatorSize+1, len(validatorSecrets)) + + // wait for consensus to start + require.NoError(t, cluster.WaitForBlock(1, 10*time.Second)) + + // register new validator + require.NoError(t, srv.RegisterValidator(newValidatorSecrets, newValidatorBalanceRaw, newValidatorStakeRaw)) + + // wait for an end of epoch so that stake gets finalized + cluster.WaitForBlock(5, 1*time.Minute) + + // start new validator + cluster.InitTestServer(t, 6, true, false) + + // assert that validators hash is correct + block, err := srv.JSONRPC().Eth().GetBlockByNumber(ethgo.Latest, false) + require.NoError(t, err) + t.Logf("Block Number=%d\n", block.Number) + + // unmarshal header extra data + extra, err := polybft.GetIbftExtra(block.ExtraData) + require.NoError(t, err) + require.NotNil(t, extra.Checkpoint) + + newValidatorAcc, err := sidechain.GetAccountFromDir(path.Join(cluster.Config.TmpDir, newValidatorSecrets)) + require.NoError(t, err) + + newValidatorAddr := newValidatorAcc.Ecdsa.Address() + + validators := polybft.AccountSet{} + // assert that new validator is among validator set + require.NoError(t, cluster.WaitUntil(10*time.Second, func() bool { + // query validators + validators, err = systemState.GetValidatorSet() + require.NoError(t, err) + + return validators.ContainsAddress((types.Address(newValidatorAddr))) + })) + + // assert that correct validators hash gets submitted + validatorsHash, err := validators.Hash() + require.NoError(t, err) + require.Equal(t, extra.Checkpoint.NextValidatorsHash, validatorsHash) + + // query registered validator + newValidatorInfo, err := sidechain.GetValidatorInfo(newValidatorAddr, txRelayer) + require.NoError(t, err) + + // assert registered validator's stake + t.Logf("New validator stake=%d\n", newValidatorInfo.TotalStake) + require.Equal(t, newValidatorStake, newValidatorInfo.TotalStake) + + // wait 3 more epochs, so that rewards get accumulated to the registered validator account + cluster.WaitForBlock(20, 2*time.Minute) + + // query registered validator + newValidatorInfo, err = sidechain.GetValidatorInfo(newValidatorAddr, txRelayer) + require.NoError(t, err) + + // assert registered validator's rewards + t.Logf("New validator rewards=%d\n", newValidatorInfo.WithdrawableRewards) + require.True(t, newValidatorInfo.WithdrawableRewards.Cmp(big.NewInt(0)) > 0) +} + +func TestE2E_Consensus_Delegation_Undelegation(t *testing.T) { + const ( + validatorSecrets = "test-chain-1" + delegatorSecrets = "test-chain-6" + premineBalance = "0x1B1AE4D6E2EF500000" // 500 native tokens (so that we have enough funds to fund delegator) + ) + + fundAmountRaw := "0xD8D726B7177A80000" // 250 native tokens + fundAmount, err := types.ParseUint256orHex(&fundAmountRaw) + require.NoError(t, err) + + cluster := framework.NewTestCluster(t, 5, + framework.WithEpochReward(100000), + framework.WithPremineValidators(premineBalance), + framework.WithEpochSize(5)) + defer cluster.Stop() + + // init delegator account + _, err = cluster.InitSecrets(delegatorSecrets, 1) + require.NoError(t, err) + + srv := cluster.Servers[0] + + txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(srv.JSONRPCAddr())) + require.NoError(t, err) + + cluster.WaitForBlock(1, 10*time.Second) + + delegatorAcc, err := sidechain.GetAccountFromDir(path.Join(cluster.Config.TmpDir, delegatorSecrets)) + require.NoError(t, err) + + delegatorAddr := delegatorAcc.Ecdsa.Address() + + validatorSecretsPath := path.Join(cluster.Config.TmpDir, validatorSecrets) + + validatorAcc, err := sidechain.GetAccountFromDir(validatorSecretsPath) + require.NoError(t, err) + + validatorAddr := validatorAcc.Ecdsa.Address() + + // fund delegator + receipt, err := txRelayer.SendTransaction(ðgo.Transaction{ + From: validatorAddr, + To: &delegatorAddr, + Value: fundAmount, + }, validatorAcc.Ecdsa) + require.NoError(t, err) + require.Equal(t, uint64(types.ReceiptSuccess), receipt.Status) + + getDelegatorInfo := func(blockNum uint64) (balance *big.Int, reward *big.Int) { + var err error + balance, err = srv.JSONRPC().Eth().GetBalance(delegatorAddr, ethgo.Latest) + require.NoError(t, err) + t.Logf("Delegator balance (block %d)=%s\n", blockNum, balance) + + reward, err = sidechain.GetDelegatorReward(validatorAddr, delegatorAddr, txRelayer) + require.NoError(t, err) + t.Logf("Delegator reward (block %d)=%s\n", blockNum, reward) + + return + } + + currentBlockNum, err := srv.JSONRPC().Eth().BlockNumber() + require.NoError(t, err) + + delegatorBalance, _ := getDelegatorInfo(currentBlockNum) + require.Equal(t, fundAmount, delegatorBalance) + + // delegate 1 native token + delegationAmount := uint64(1e18) + require.NoError(t, srv.Delegate(delegationAmount, validatorSecretsPath, validatorAddr)) + + // wait for 2 epochs to accumulate delegator rewards + cluster.WaitForBlock(10, 1*time.Minute) + + // query delegator rewards + _, delegatorReward := getDelegatorInfo(10) + // there should be at least 80 weis delegator rewards accumulated + // (80 weis per epoch is accumulated if validator signs only single block in entire epoch) + require.Greater(t, delegatorReward.Uint64(), uint64(80)) + + // undelegate rewards + require.NoError(t, srv.Undelegate(delegationAmount, validatorSecretsPath, validatorAddr)) + t.Logf("Rewards are undelegated\n") + + currentBlockNum, err = srv.JSONRPC().Eth().BlockNumber() + require.NoError(t, err) + getDelegatorInfo(currentBlockNum) + + // wait for one epoch to be able to withdraw undelegated funds + require.NoError(t, cluster.WaitForBlock(15, time.Minute)) + + // withdraw available rewards + require.NoError(t, srv.Withdraw(validatorSecretsPath, delegatorAddr)) + t.Logf("Funds are withdrawn\n") + + currentBlockNum, err = srv.JSONRPC().Eth().BlockNumber() + require.NoError(t, err) + getDelegatorInfo(currentBlockNum) + + // wait for single epoch to process withdrawal + cluster.WaitForBlock(20, time.Minute) + + currentBlockNum, err = srv.JSONRPC().Eth().BlockNumber() + require.NoError(t, err) + + // assert that delegator doesn't receive any rewards + _, delegatorReward = getDelegatorInfo(currentBlockNum) + // TODO: Check (for some reason delegator still gets rewards although it withdraws entire delegation amount) + require.True(t, delegatorReward.Cmp(big.NewInt(0)) > 0) +} + +func TestE2E_Consensus_Validator_Unstake(t *testing.T) { + cluster := framework.NewTestCluster(t, 5, + framework.WithBridge(), + framework.WithEpochReward(10000), + framework.WithEpochSize(5), + framework.WithPremineValidators("10000000000000000000")) // 10 native tokens + validatorSecrets := path.Join(cluster.Config.TmpDir, "test-chain-1") + srv := cluster.Servers[0] + + txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(srv.JSONRPCAddr())) + require.NoError(t, err) + + systemState := polybft.NewSystemState( + &polybft.PolyBFTConfig{ + StateReceiverAddr: contracts.StateReceiverContract, + ValidatorSetAddr: contracts.ValidatorSetContract}, + &e2eStateProvider{txRelayer: txRelayer}) + + validatorAcc, err := sidechain.GetAccountFromDir(validatorSecrets) + require.NoError(t, err) + + validatorAddr := validatorAcc.Ecdsa.Address() + + // wait for one epoch to accumulate validator rewards + require.NoError(t, cluster.WaitForBlock(5, 20*time.Second)) + + validatorInfo, err := sidechain.GetValidatorInfo(validatorAddr, txRelayer) + require.NoError(t, err) + + initialStake := validatorInfo.TotalStake + t.Logf("Stake (before unstake)=%d\n", initialStake) + + // unstake entire balance (which should remove validator from the validator set in next epoch) + require.NoError(t, srv.Unstake(initialStake.Uint64())) + + // wait end of epoch + require.NoError(t, cluster.WaitForBlock(10, 20*time.Second)) + + validatorSet, err := systemState.GetValidatorSet() + require.NoError(t, err) + + // assert that validator isn't present in new validator set + require.Equal(t, 4, validatorSet.Len()) + + validatorInfo, err = sidechain.GetValidatorInfo(validatorAddr, txRelayer) + require.NoError(t, err) + + reward := validatorInfo.WithdrawableRewards + t.Logf("Rewards=%d\n", reward) + t.Logf("Stake (after unstake)=%d\n", validatorInfo.TotalStake) + require.Greater(t, reward.Uint64(), uint64(0)) + + oldValidatorBalance, err := srv.JSONRPC().Eth().GetBalance(validatorAcc.Ecdsa.Address(), ethgo.Latest) + require.NoError(t, err) + t.Logf("Balance (before withdrawal)=%s\n", oldValidatorBalance) + + // withdraw (stake + rewards) + require.NoError(t, srv.Withdraw(validatorSecrets, validatorAddr)) + + newValidatorBalance, err := srv.JSONRPC().Eth().GetBalance(validatorAcc.Ecdsa.Address(), ethgo.Latest) + require.NoError(t, err) + t.Logf("Balance (after withdrawal)=%s\n", newValidatorBalance) + require.True(t, newValidatorBalance.Cmp(oldValidatorBalance) > 0) + + l1Relayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(cluster.Bridge.JSONRPCAddr())) + require.NoError(t, err) + + manifest, err := polybft.LoadManifest(path.Join(cluster.Config.TmpDir, manifestFileName)) + require.NoError(t, err) + + checkpointManagerAddr := ethgo.Address(manifest.RootchainConfig.CheckpointManagerAddress) + rootchainSender := ethgo.Address(manifest.RootchainConfig.AdminAddress) + + // query rootchain validator set and make sure that validator which unstaked all the funds isn't present in validator set anymore + // (execute it multiple times if needed, because it is unknown in advance how much time it is going to take until checkpoint is submitted) + rootchainValidators := []*polybft.ValidatorInfo{} + err = cluster.Bridge.WaitUntil(time.Second, 10*time.Second, func() (bool, error) { + rootchainValidators, err = getRootchainValidators(l1Relayer, checkpointManagerAddr, rootchainSender) + if err != nil { + return true, err + } + + return len(rootchainValidators) == 4, nil + }) + require.NoError(t, err) + require.Equal(t, 4, len(rootchainValidators)) + + for _, validator := range rootchainValidators { + if validator.Address == validatorAddr { + t.Fatalf("not expected to find validator %v in the current validator set", validator.Address) + } + } +} diff --git a/e2e-polybft/framework/node.go b/e2e-polybft/framework/node.go new file mode 100644 index 0000000000..8cac0d299b --- /dev/null +++ b/e2e-polybft/framework/node.go @@ -0,0 +1,77 @@ +package framework + +import ( + "io" + "os" + "os/exec" + "sync/atomic" +) + +type node struct { + shuttingDown uint64 + cmd *exec.Cmd + doneCh chan struct{} + exitResult *exitResult +} + +func newNode(binary string, args []string, stdout io.Writer) (*node, error) { + cmd := exec.Command(binary, args...) + cmd.Stdout = stdout + cmd.Stderr = stdout + + if err := cmd.Start(); err != nil { + return nil, err + } + + n := &node{ + cmd: cmd, + doneCh: make(chan struct{}), + } + go n.run() + + return n, nil +} + +func (n *node) ExitResult() *exitResult { + return n.exitResult +} + +func (n *node) Wait() <-chan struct{} { + return n.doneCh +} + +func (n *node) run() { + err := n.cmd.Wait() + + n.exitResult = &exitResult{ + Signaled: n.IsShuttingDown(), + Err: err, + } + close(n.doneCh) + n.cmd = nil +} + +func (n *node) IsShuttingDown() bool { + return atomic.LoadUint64(&n.shuttingDown) == 1 +} + +func (n *node) Stop() error { + if n.cmd == nil { + // the server is already stopped + return nil + } + + if err := n.cmd.Process.Signal(os.Interrupt); err != nil { + return err + } + + atomic.StoreUint64(&n.shuttingDown, 1) + <-n.Wait() + + return nil +} + +type exitResult struct { + Signaled bool + Err error +} diff --git a/e2e-polybft/framework/test-bridge.go b/e2e-polybft/framework/test-bridge.go new file mode 100644 index 0000000000..1e586e9bde --- /dev/null +++ b/e2e-polybft/framework/test-bridge.go @@ -0,0 +1,123 @@ +package framework + +import ( + "fmt" + "path" + "strconv" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/command/rootchain/server" +) + +type TestBridge struct { + t *testing.T + clusterConfig *TestClusterConfig + node *node +} + +func NewTestBridge(t *testing.T, clusterConfig *TestClusterConfig) (*TestBridge, error) { + t.Helper() + + bridge := &TestBridge{ + t: t, + clusterConfig: clusterConfig, + } + + err := bridge.Start() + if err != nil { + return nil, err + } + + return bridge, nil +} + +func (t *TestBridge) Start() error { + // Build arguments + args := []string{ + "rootchain", + "server", + "--data-dir", t.clusterConfig.Dir("test-rootchain"), + } + + stdout := t.clusterConfig.GetStdout("bridge") + + bridgeNode, err := newNode(t.clusterConfig.Binary, args, stdout) + if err != nil { + return err + } + + t.node = bridgeNode + + if err = server.PingServer(nil); err != nil { + return err + } + + return nil +} + +func (t *TestBridge) deployRootchainContracts(manifestPath string) error { + args := []string{ + "rootchain", + "init-contracts", + "--path", t.clusterConfig.ContractsDir, + "--manifest", manifestPath, + } + + err := runCommand(t.clusterConfig.Binary, args, t.clusterConfig.GetStdout("bridge")) + if err != nil { + return fmt.Errorf("failed to deploy rootchain contracts: %w", err) + } + + return nil +} + +func (t *TestBridge) fundValidators() error { + args := []string{ + "rootchain", + "fund", + "--data-dir", path.Join(t.clusterConfig.TmpDir, t.clusterConfig.ValidatorPrefix), + "--num", strconv.Itoa(int(t.clusterConfig.ValidatorSetSize) + t.clusterConfig.NonValidatorCount), + } + + err := runCommand(t.clusterConfig.Binary, args, t.clusterConfig.GetStdout("bridge")) + if err != nil { + return fmt.Errorf("failed to deploy fund validators: %w", err) + } + + return nil +} + +func (t *TestBridge) Stop() { + if err := t.node.Stop(); err != nil { + t.t.Error(err) + } + + t.node = nil +} + +func (t *TestBridge) JSONRPCAddr() string { + return fmt.Sprintf("http://%s:%d", hostIP, 8545) +} + +func (t *TestBridge) WaitUntil(pollFrequency, timeout time.Duration, handler func() (bool, error)) error { + timer := time.NewTimer(timeout) + defer timer.Stop() + + for { + select { + case <-timer.C: + return fmt.Errorf("timeout") + case <-time.After(pollFrequency): + } + + isConditionMet, err := handler() + if err != nil { + return err + } + + if isConditionMet { + return nil + } + } +} diff --git a/e2e-polybft/framework/test-cluster.go b/e2e-polybft/framework/test-cluster.go new file mode 100644 index 0000000000..3cf9e0d853 --- /dev/null +++ b/e2e-polybft/framework/test-cluster.go @@ -0,0 +1,584 @@ +package framework + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "os/exec" + "path" + "path/filepath" + "regexp" + "strconv" + "strings" + "sync" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/command" + "github.com/0xPolygon/polygon-edge/command/genesis" + "github.com/0xPolygon/polygon-edge/command/rootchain/helper" + "github.com/0xPolygon/polygon-edge/helper/common" + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/require" +) + +const ( + // envE2ETestsEnabled signal whether the e2e tests will run + envE2ETestsEnabled = "E2E_TESTS" + + // envLogsEnabled signal whether the output of the nodes get piped to a log file + envLogsEnabled = "E2E_LOGS" + + // envLogLevel specifies log level of each node + envLogLevel = "E2E_LOG_LEVEL" + + // envStdoutEnabled signal whether the output of the nodes get piped to stdout + envStdoutEnabled = "E2E_STDOUT" + + // property based tests enabled + envPropertyBaseTestEnabled = "E2E_PROPERTY_TESTS" +) + +const ( + // path to core contracts + defaultContractsPath = "./../core-contracts/artifacts/contracts/" + + // prefix for validator directory + defaultValidatorPrefix = "test-chain-" +) + +var startTime int64 + +func init() { + startTime = time.Now().UnixMilli() +} + +func resolveBinary() string { + bin := os.Getenv("EDGE_BINARY") + if bin != "" { + return bin + } + // fallback + return "polygon-edge" +} + +type TestClusterConfig struct { + t *testing.T + + Name string + Premine []string // address[:amount] + PremineValidators string + HasBridge bool + BootnodeCount int + NonValidatorCount int + WithLogs bool + WithStdout bool + LogsDir string + TmpDir string + BlockGasLimit uint64 + ContractsDir string + ValidatorPrefix string + Binary string + ValidatorSetSize uint64 + EpochSize int + EpochReward int + PropertyBaseTests bool + SecretsCallback func([]types.Address, *TestClusterConfig) + + logsDirOnce sync.Once +} + +func (c *TestClusterConfig) Dir(name string) string { + return filepath.Join(c.TmpDir, name) +} + +func (c *TestClusterConfig) GetStdout(name string, custom ...io.Writer) io.Writer { + writers := []io.Writer{} + + if c.WithLogs { + c.logsDirOnce.Do(func() { + c.initLogsDir() + }) + + f, err := os.OpenFile(filepath.Join(c.LogsDir, name+".log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600) + if err != nil { + c.t.Fatal(err) + } + + writers = append(writers, f) + + c.t.Cleanup(func() { + err = f.Close() + if err != nil { + c.t.Logf("Failed to close file. Error: %s", err) + } + }) + } + + if c.WithStdout { + writers = append(writers, os.Stdout) + } + + if len(custom) > 0 { + writers = append(writers, custom...) + } + + if len(writers) == 0 { + return io.Discard + } + + return io.MultiWriter(writers...) +} + +func (c *TestClusterConfig) initLogsDir() { + logsDir := path.Join("..", fmt.Sprintf("e2e-logs-%d", startTime), c.t.Name()) + + if err := common.CreateDirSafe(logsDir, 0750); err != nil { + c.t.Fatal(err) + } + + c.t.Logf("logs enabled for e2e test: %s", logsDir) + c.LogsDir = logsDir +} + +type TestCluster struct { + Config *TestClusterConfig + Servers []*TestServer + Bridge *TestBridge + initialPort int64 + + once sync.Once + failCh chan struct{} + executionErr error +} + +type ClusterOption func(*TestClusterConfig) + +func WithPremine(addresses ...types.Address) ClusterOption { + return func(h *TestClusterConfig) { + for _, a := range addresses { + h.Premine = append(h.Premine, a.String()) + } + } +} + +func WithPremineValidators(premineBalance string) ClusterOption { + return func(h *TestClusterConfig) { + h.PremineValidators = premineBalance + } +} + +func WithSecretsCallback(fn func([]types.Address, *TestClusterConfig)) ClusterOption { + return func(h *TestClusterConfig) { + h.SecretsCallback = fn + } +} + +func WithBridge() ClusterOption { + return func(h *TestClusterConfig) { + h.HasBridge = true + } +} + +func WithNonValidators(num int) ClusterOption { + return func(h *TestClusterConfig) { + h.NonValidatorCount = num + } +} + +func WithValidatorSnapshot(validatorsLen uint64) ClusterOption { + return func(h *TestClusterConfig) { + h.ValidatorSetSize = validatorsLen + } +} + +func WithBootnodeCount(cnt int) ClusterOption { + return func(h *TestClusterConfig) { + h.BootnodeCount = cnt + } +} + +func WithEpochSize(epochSize int) ClusterOption { + return func(h *TestClusterConfig) { + h.EpochSize = epochSize + } +} + +func WithEpochReward(epochReward int) ClusterOption { + return func(h *TestClusterConfig) { + h.EpochReward = epochReward + } +} + +func WithBlockGasLimit(blockGasLimit uint64) ClusterOption { + return func(h *TestClusterConfig) { + h.BlockGasLimit = blockGasLimit + } +} + +func WithPropertyBaseTests(propertyBaseTests bool) ClusterOption { + return func(h *TestClusterConfig) { + h.PropertyBaseTests = propertyBaseTests + } +} + +func isTrueEnv(e string) bool { + return strings.ToLower(os.Getenv(e)) == "true" +} + +func NewTestCluster(t *testing.T, validatorsCount int, opts ...ClusterOption) *TestCluster { + t.Helper() + + var err error + + config := &TestClusterConfig{ + t: t, + WithLogs: isTrueEnv(envLogsEnabled), + WithStdout: isTrueEnv(envStdoutEnabled), + Binary: resolveBinary(), + EpochSize: 10, + EpochReward: 1, + BlockGasLimit: 1e7, // 10M + PremineValidators: command.DefaultPremineBalance, + } + + if config.ContractsDir == "" { + config.ContractsDir = defaultContractsPath + } + + if config.ValidatorPrefix == "" { + config.ValidatorPrefix = defaultValidatorPrefix + } + + for _, opt := range opts { + opt(config) + } + + if !config.PropertyBaseTests && !isTrueEnv(envE2ETestsEnabled) || + config.PropertyBaseTests && !isTrueEnv(envPropertyBaseTestEnabled) { + t.Skip("Integration tests are disabled.") + } + + config.TmpDir, err = os.MkdirTemp("/tmp", "e2e-polybft-") + require.NoError(t, err) + + cluster := &TestCluster{ + Servers: []*TestServer{}, + Config: config, + initialPort: 30300, + failCh: make(chan struct{}), + once: sync.Once{}, + } + + { + // run init accounts + addresses, err := cluster.InitSecrets(cluster.Config.ValidatorPrefix, validatorsCount) + require.NoError(t, err) + + if cluster.Config.SecretsCallback != nil { + cluster.Config.SecretsCallback(addresses, cluster.Config) + } + } + + manifestPath := path.Join(config.TmpDir, "manifest.json") + args := []string{ + "manifest", + "--path", manifestPath, + "--validators-path", config.TmpDir, + "--validators-prefix", cluster.Config.ValidatorPrefix, + "--premine-validators", cluster.Config.PremineValidators, + } + // run manifest file creation + require.NoError(t, cluster.cmdRun(args...)) + + if cluster.Config.HasBridge { + // start bridge + cluster.Bridge, err = NewTestBridge(t, cluster.Config) + require.NoError(t, err) + } + + // in case no validators are specified in opts, all nodes will be validators + if cluster.Config.ValidatorSetSize == 0 { + cluster.Config.ValidatorSetSize = uint64(validatorsCount) + } + + if cluster.Config.HasBridge { + err := cluster.Bridge.deployRootchainContracts(manifestPath) + require.NoError(t, err) + + err = cluster.Bridge.fundValidators() + require.NoError(t, err) + } + + { + // run genesis configuration population + args := []string{ + "genesis", + "--manifest", manifestPath, + "--consensus", "polybft", + "--dir", path.Join(config.TmpDir, "genesis.json"), + "--contracts-path", defaultContractsPath, + "--block-gas-limit", strconv.FormatUint(cluster.Config.BlockGasLimit, 10), + "--epoch-size", strconv.Itoa(cluster.Config.EpochSize), + "--epoch-reward", strconv.Itoa(cluster.Config.EpochReward), + "--premine", "0x0000000000000000000000000000000000000000", + } + + if len(cluster.Config.Premine) != 0 { + for _, premine := range cluster.Config.Premine { + args = append(args, "--premine", premine) + } + } + + if cluster.Config.HasBridge { + rootchainIP, err := helper.ReadRootchainIP() + require.NoError(t, err) + args = append(args, "--bridge-json-rpc", rootchainIP) + } + + validators, err := genesis.ReadValidatorsByPrefix(cluster.Config.TmpDir, cluster.Config.ValidatorPrefix) + require.NoError(t, err) + + if cluster.Config.BootnodeCount > 0 { + cnt := cluster.Config.BootnodeCount + if len(validators) < cnt { + cnt = len(validators) + } + + for i := 0; i < cnt; i++ { + maddr := fmt.Sprintf("/ip4/%s/tcp/%d/p2p/%s", + "127.0.0.1", cluster.initialPort+int64(i+1), validators[i].NodeID) + args = append(args, "--bootnode", maddr) + } + } + + if cluster.Config.ValidatorSetSize > 0 { + args = append(args, "--validator-set-size", fmt.Sprint(cluster.Config.ValidatorSetSize)) + } + + // run cmd init-genesis with all the arguments + err = cluster.cmdRun(args...) + require.NoError(t, err) + } + + for i := 1; i <= int(cluster.Config.ValidatorSetSize); i++ { + cluster.InitTestServer(t, i, true, cluster.Config.HasBridge && i == 1 /* relayer */) + } + + for i := 1; i <= cluster.Config.NonValidatorCount; i++ { + offsetIndex := i + int(cluster.Config.ValidatorSetSize) + cluster.InitTestServer(t, offsetIndex, false, false /* relayer */) + } + + return cluster +} + +func (c *TestCluster) InitTestServer(t *testing.T, i int, isValidator bool, relayer bool) { + t.Helper() + + logLevel := os.Getenv(envLogLevel) + dataDir := c.Config.Dir(c.Config.ValidatorPrefix + strconv.Itoa(i)) + + srv := NewTestServer(t, c.Config, func(config *TestServerConfig) { + config.DataDir = dataDir + config.Seal = isValidator + config.Chain = c.Config.Dir("genesis.json") + config.P2PPort = c.getOpenPort() + config.LogLevel = logLevel + config.Relayer = relayer + }) + + // watch the server for stop signals. It is important to fix the specific + // 'node' reference since 'TestServer' creates a new one if restarted. + go func(node *node) { + <-node.Wait() + + if !node.ExitResult().Signaled { + c.Fail(fmt.Errorf("server at dir '%s' has stopped unexpectedly", dataDir)) + } + }(srv.node) + + c.Servers = append(c.Servers, srv) +} + +func (c *TestCluster) cmdRun(args ...string) error { + return runCommand(c.Config.Binary, args, c.Config.GetStdout(args[0])) +} + +// EmitTransfer function is used to invoke e2e rootchain emit command +// with appropriately created wallets and amounts for test transactions +func (c *TestCluster) EmitTransfer(contractAddress, walletAddresses, amounts string) error { + if len(contractAddress) == 0 { + return errors.New("provide contractAddress value") + } + + if len(walletAddresses) == 0 { + return errors.New("provide at least one wallet address value") + } + + if len(amounts) == 0 { + return errors.New("provide at least one amount value") + } + + return c.cmdRun("rootchain", + "emit", + "--manifest", path.Join(c.Config.TmpDir, "manifest.json"), + "--contract", contractAddress, + "--wallets", walletAddresses, + "--amounts", amounts) +} + +func (c *TestCluster) Fail(err error) { + c.once.Do(func() { + c.executionErr = err + close(c.failCh) + }) +} + +func (c *TestCluster) Stop() { + if c.Bridge != nil { + c.Bridge.Stop() + } + + for _, srv := range c.Servers { + if srv.isRunning() { + srv.Stop() + } + } +} + +func (c *TestCluster) Stats(t *testing.T) { + t.Helper() + + for index, i := range c.Servers { + if !i.isRunning() { + continue + } + + num, err := i.JSONRPC().Eth().BlockNumber() + t.Log("Stats node", index, "err", err, "block", num, "validator", i.config.Seal) + } +} + +func (c *TestCluster) WaitUntil(dur time.Duration, handler func() bool) error { + timer := time.NewTimer(dur) + defer timer.Stop() + + for { + select { + case <-timer.C: + return fmt.Errorf("timeout") + case <-c.failCh: + return c.executionErr + case <-time.After(2 * time.Second): + } + + if handler() { + return nil + } + } +} + +func (c *TestCluster) WaitForBlock(n uint64, timeout time.Duration) error { + timer := time.NewTimer(timeout) + + ok := false + for !ok { + select { + case <-timer.C: + return fmt.Errorf("wait for block timeout") + case <-time.After(2 * time.Second): + } + + ok = true + + for _, i := range c.Servers { + if !i.isRunning() { + continue + } + + num, err := i.JSONRPC().Eth().BlockNumber() + + if err != nil || num < n { + ok = false + + break + } + } + } + + return nil +} + +// WaitForGeneric waits until all running servers returns true from fn callback or timeout defined by dur occurs +func (c *TestCluster) WaitForGeneric(dur time.Duration, fn func(*TestServer) bool) error { + return c.WaitUntil(dur, func() bool { + for _, srv := range c.Servers { + // query only running servers + if srv.isRunning() && !fn(srv) { + return false + } + } + + return true + }) +} + +func (c *TestCluster) getOpenPort() int64 { + c.initialPort++ + + return c.initialPort +} + +// runCommand executes command with given arguments +func runCommand(binary string, args []string, stdout io.Writer) error { + var stdErr bytes.Buffer + + cmd := exec.Command(binary, args...) + cmd.Stderr = &stdErr + cmd.Stdout = stdout + + if err := cmd.Run(); err != nil { + if stdErr.Len() > 0 { + return fmt.Errorf("failed to execute command: %s", stdErr.String()) + } + + return fmt.Errorf("failed to execute command: %w", err) + } + + if stdErr.Len() > 0 { + return fmt.Errorf("error during command execution: %s", stdErr.String()) + } + + return nil +} + +// InitSecrets initializes account(s) secrets with given prefix. +// (secrets are being stored in the temp directory created by given e2e test execution) +func (c *TestCluster) InitSecrets(prefix string, count int) ([]types.Address, error) { + var b bytes.Buffer + + args := []string{ + "polybft-secrets", + "--data-dir", path.Join(c.Config.TmpDir, prefix), + "--num", strconv.Itoa(count), + "--insecure", + } + stdOut := c.Config.GetStdout("polybft-secrets", &b) + + if err := runCommand(c.Config.Binary, args, stdOut); err != nil { + return nil, err + } + + re := regexp.MustCompile("\\(address\\) = 0x([a-fA-F0-9]+)") + parsed := re.FindAllStringSubmatch(b.String(), -1) + result := make([]types.Address, len(parsed)) + + for i, v := range parsed { + result[i] = types.StringToAddress(v[1]) + } + + return result, nil +} diff --git a/e2e-polybft/framework/test-server.go b/e2e-polybft/framework/test-server.go new file mode 100644 index 0000000000..32fe384ae5 --- /dev/null +++ b/e2e-polybft/framework/test-server.go @@ -0,0 +1,241 @@ +package framework + +import ( + "fmt" + "io/ioutil" + "path" + "strconv" + "sync/atomic" + "testing" + + "github.com/0xPolygon/polygon-edge/server/proto" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/jsonrpc" + "google.golang.org/grpc" +) + +type TestServerConfig struct { + Name string + JSONRPCPort int64 + GRPCPort int64 + P2PPort int64 + Seal bool + DataDir string + Chain string + LogLevel string + Relayer bool +} + +type TestServerConfigCallback func(*TestServerConfig) + +const hostIP = "127.0.0.1" + +var initialPortForServer = int64(12000) + +func getOpenPortForServer() int64 { + return atomic.AddInt64(&initialPortForServer, 1) +} + +type TestServer struct { + t *testing.T + + clusterConfig *TestClusterConfig + config *TestServerConfig + node *node +} + +func (t *TestServer) GrpcAddr() string { + return fmt.Sprintf("%s:%d", hostIP, t.config.GRPCPort) +} + +func (t *TestServer) JSONRPCAddr() string { + return fmt.Sprintf("http://%s:%d", hostIP, t.config.JSONRPCPort) +} + +func (t *TestServer) JSONRPC() *jsonrpc.Client { + clt, err := jsonrpc.NewClient(t.JSONRPCAddr()) + if err != nil { + t.t.Fatal(err) + } + + return clt +} + +func (t *TestServer) Conn() proto.SystemClient { + conn, err := grpc.Dial(t.GrpcAddr(), grpc.WithInsecure()) + if err != nil { + t.t.Fatal(err) + } + + return proto.NewSystemClient(conn) +} + +func NewTestServer(t *testing.T, clusterConfig *TestClusterConfig, callback TestServerConfigCallback) *TestServer { + t.Helper() + + config := &TestServerConfig{ + Name: uuid.New().String(), + JSONRPCPort: getOpenPortForServer(), + GRPCPort: getOpenPortForServer(), + P2PPort: getOpenPortForServer(), + } + + if callback != nil { + callback(config) + } + + if config.DataDir == "" { + dataDir, err := ioutil.TempDir("/tmp", "edge-e2e-") + assert.NoError(t, err) + + config.DataDir = dataDir + } + + srv := &TestServer{ + clusterConfig: clusterConfig, + t: t, + config: config, + } + srv.Start() + + return srv +} + +func (t *TestServer) isRunning() bool { + return t.node != nil +} + +func (t *TestServer) Start() { + config := t.config + + // Build arguments + args := []string{ + "server", + // add data dir + "--data-dir", config.DataDir, + // add custom chain + "--chain", config.Chain, + // enable p2p port + "--libp2p", fmt.Sprintf(":%d", config.P2PPort), + // grpc port + "--grpc-address", fmt.Sprintf("localhost:%d", config.GRPCPort), + // enable jsonrpc + "--jsonrpc", fmt.Sprintf(":%d", config.JSONRPCPort), + } + + if len(config.LogLevel) > 0 { + args = append(args, "--log-level", config.LogLevel) + } + + if config.Seal { + args = append(args, "--seal") + } + + if config.Relayer { + args = append(args, "--relayer") + } + + // Start the server + stdout := t.clusterConfig.GetStdout(t.config.Name) + + node, err := newNode(t.clusterConfig.Binary, args, stdout) + if err != nil { + t.t.Fatal(err) + } + + t.node = node +} + +func (t *TestServer) Stop() { + if err := t.node.Stop(); err != nil { + t.t.Fatal(err) + } + + t.node = nil +} + +// Stake stakes given amount to validator account encapsulated by given server instance +func (t *TestServer) Stake(amount uint64) error { + args := []string{ + "polybft", + "stake", + "--account", t.config.DataDir, + "--jsonrpc", t.JSONRPCAddr(), + "--amount", strconv.FormatUint(amount, 10), + "--self", + } + + return runCommand(t.clusterConfig.Binary, args, t.clusterConfig.GetStdout("stake")) +} + +// Unstake unstakes given amount from validator account encapsulated by given server instance +func (t *TestServer) Unstake(amount uint64) error { + args := []string{ + "polybft", + "unstake", + "--account", t.config.DataDir, + "--jsonrpc", t.JSONRPCAddr(), + "--amount", strconv.FormatUint(amount, 10), + "--self", + } + + return runCommand(t.clusterConfig.Binary, args, t.clusterConfig.GetStdout("stake")) +} + +// RegisterValidator is a wrapper function which registers new validator with given balance and stake +func (t *TestServer) RegisterValidator(secrets string, balance string, stake string) error { + args := []string{ + "polybft", + "register-validator", + "--data-dir", path.Join(t.clusterConfig.TmpDir, secrets), + "--registrator-data-dir", path.Join(t.clusterConfig.TmpDir, "test-chain-1"), + "--jsonrpc", t.JSONRPCAddr(), + "--balance", balance, + "--stake", stake, + } + + return runCommand(t.clusterConfig.Binary, args, t.clusterConfig.GetStdout("register-validator")) +} + +// Delegate delegates given amount by the account in secrets to validatorAddr validator +func (t *TestServer) Delegate(amount uint64, secrets string, validatorAddr ethgo.Address) error { + args := []string{ + "polybft", + "stake", + "--account", secrets, + "--jsonrpc", t.JSONRPCAddr(), + "--delegate", validatorAddr.String(), + "--amount", strconv.FormatUint(amount, 10), + } + + return runCommand(t.clusterConfig.Binary, args, t.clusterConfig.GetStdout("delegation")) +} + +// Undelegate undelegates given amount by the account in secrets from validatorAddr validator +func (t *TestServer) Undelegate(amount uint64, secrets string, validatorAddr ethgo.Address) error { + args := []string{ + "polybft", + "unstake", + "--account", secrets, + "--undelegate", validatorAddr.String(), + "--amount", strconv.FormatUint(amount, 10), + "--jsonrpc", t.JSONRPCAddr(), + } + + return runCommand(t.clusterConfig.Binary, args, t.clusterConfig.GetStdout("delegation")) +} + +// Withdraw withdraws available balance to provided recipient address +func (t *TestServer) Withdraw(secrets string, recipient ethgo.Address) error { + args := []string{ + "polybft", + "withdraw", + "--account", secrets, + "--to", recipient.String(), + "--jsonrpc", t.JSONRPCAddr(), + } + + return runCommand(t.clusterConfig.Binary, args, t.clusterConfig.GetStdout("withdrawal")) +} diff --git a/e2e-polybft/helpers_test.go b/e2e-polybft/helpers_test.go new file mode 100644 index 0000000000..04f9e495f8 --- /dev/null +++ b/e2e-polybft/helpers_test.go @@ -0,0 +1,79 @@ +package e2e + +import ( + "errors" + "math/big" + + "github.com/0xPolygon/polygon-edge/consensus/polybft" + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/helper/hex" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/contract" +) + +type e2eStateProvider struct { + txRelayer txrelayer.TxRelayer +} + +func (s *e2eStateProvider) Call(contractAddr ethgo.Address, input []byte, opts *contract.CallOpts) ([]byte, error) { + response, err := s.txRelayer.Call(ethgo.Address(types.ZeroAddress), contractAddr, input) + if err != nil { + return nil, err + } + + return hex.DecodeHex(response) +} + +func (s *e2eStateProvider) Txn(ethgo.Address, ethgo.Key, []byte) (contract.Txn, error) { + return nil, errors.New("send txn is not supported") +} + +// getRootchainValidators queries rootchain validator set +func getRootchainValidators(relayer txrelayer.TxRelayer, checkpointManagerAddr, sender ethgo.Address) ([]*polybft.ValidatorInfo, error) { + validatorsCountRaw, err := ABICall(relayer, contractsapi.CheckpointManager, + checkpointManagerAddr, sender, "currentValidatorSetLength") + if err != nil { + return nil, err + } + + validatorsCount, err := types.ParseUint64orHex(&validatorsCountRaw) + if err != nil { + return nil, err + } + + currentValidatorSetMethod := contractsapi.CheckpointManager.Abi.GetMethod("currentValidatorSet") + validators := make([]*polybft.ValidatorInfo, validatorsCount) + + for i := 0; i < int(validatorsCount); i++ { + validatorRaw, err := ABICall(relayer, contractsapi.CheckpointManager, + checkpointManagerAddr, sender, "currentValidatorSet", i) + if err != nil { + return nil, err + } + + validatorSetRaw, err := hex.DecodeString(validatorRaw[2:]) + if err != nil { + return nil, err + } + + decodedResults, err := currentValidatorSetMethod.Outputs.Decode(validatorSetRaw) + if err != nil { + return nil, err + } + + results, ok := decodedResults.(map[string]interface{}) + if !ok { + return nil, errors.New("failed to decode validator") + } + + //nolint:forcetypeassert + validators[i] = &polybft.ValidatorInfo{ + Address: results["_address"].(ethgo.Address), + TotalStake: results["votingPower"].(*big.Int), + } + } + + return validators, nil +} diff --git a/e2e-polybft/network_test.go b/e2e-polybft/network_test.go new file mode 100644 index 0000000000..e1f5128ac3 --- /dev/null +++ b/e2e-polybft/network_test.go @@ -0,0 +1,38 @@ +package e2e + +import ( + "context" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/e2e-polybft/framework" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/emptypb" +) + +func TestE2E_NetworkDiscoveryProtocol(t *testing.T) { + const ( + validatorCount = 5 + nonValidatorCount = 5 + // each node in cluster should find at least 2 more peers beside bootnode + atLeastPeers = 3 + testTimeout = time.Second * 60 + ) + + // create cluster + cluster := framework.NewTestCluster(t, 10, + framework.WithValidatorSnapshot(validatorCount), + framework.WithNonValidators(nonValidatorCount), + framework.WithBootnodeCount(1)) + defer cluster.Stop() + + ctx := context.Background() + + // wait for everyone to have at least 'atLeastPeers' peers + err := cluster.WaitForGeneric(testTimeout, func(ts *framework.TestServer) bool { + peerList, err := ts.Conn().PeersList(ctx, &emptypb.Empty{}) + + return err == nil && len(peerList.GetPeers()) >= atLeastPeers + }) + assert.NoError(t, err) +} diff --git a/e2e-polybft/property_test.go b/e2e-polybft/property_test.go new file mode 100644 index 0000000000..38a81d52c4 --- /dev/null +++ b/e2e-polybft/property_test.go @@ -0,0 +1,49 @@ +package e2e + +import ( + "fmt" + "math" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/e2e-polybft/framework" + "github.com/0xPolygon/polygon-edge/types" + "pgregory.net/rapid" +) + +func TestProperty_DifferentVotingPower(t *testing.T) { + t.Parallel() + + const ( + blockTime = time.Second * 6 + maxPremine = math.MaxUint64 + ) + + rapid.Check(t, func(tt *rapid.T) { + var ( + numNodes = rapid.Uint64Range(4, 8).Draw(tt, "number of cluster nodes") + epochSize = rapid.OneOf(rapid.Just(4), rapid.Just(10)).Draw(tt, "epoch size") + numBlocks = rapid.Uint64Range(2, 5).Draw(tt, "number of blocks the cluster should mine") + ) + + premine := make([]uint64, numNodes) + + // premined amount will determine validator's stake and therefore voting power + for i := range premine { + premine[i] = rapid.Uint64Range(1, maxPremine).Draw(tt, fmt.Sprintf("stake for node %d", i+1)) + } + + cluster := framework.NewTestCluster(t, int(numNodes), + framework.WithEpochSize(epochSize), + framework.WithPropertyBaseTests(true), + framework.WithSecretsCallback(func(adresses []types.Address, config *framework.TestClusterConfig) { + for i, a := range adresses { + config.Premine = append(config.Premine, fmt.Sprintf("%s:%d", a, premine[i])) + } + })) + defer cluster.Stop() + + // wait for single epoch to process withdrawal + cluster.WaitForBlock(numBlocks, blockTime*time.Duration(numBlocks)) + }) +} diff --git a/e2e-polybft/txpool_test.go b/e2e-polybft/txpool_test.go new file mode 100644 index 0000000000..7b72f87224 --- /dev/null +++ b/e2e-polybft/txpool_test.go @@ -0,0 +1,205 @@ +package e2e + +import ( + "math/big" + "sync" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/e2e-polybft/framework" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/jsonrpc" + "github.com/umbracle/ethgo/wallet" +) + +func TestE2E_TxPool_Transfer(t *testing.T) { + // premine an account in the genesis file + sender, err := wallet.GenerateKey() + require.NoError(t, err) + + cluster := framework.NewTestCluster(t, 5, framework.WithPremine(types.Address(sender.Address()))) + defer cluster.Stop() + + require.NoError(t, cluster.WaitForBlock(2, 1*time.Minute)) + + client := cluster.Servers[0].JSONRPC().Eth() + + sendAmount := 1 + num := 20 + + receivers := []ethgo.Address{} + + for i := 0; i < num; i++ { + key, err := wallet.GenerateKey() + require.NoError(t, err) + + receivers = append(receivers, key.Address()) + } + + var wg sync.WaitGroup + for i := 0; i < num; i++ { + wg.Add(1) + + go func(i int, to ethgo.Address) { + defer wg.Done() + + gasPrice, err := client.GasPrice() + require.NoError(t, err) + + txn := ðgo.Transaction{ + From: sender.Address(), + To: &to, + GasPrice: gasPrice, + Gas: 30000, // enough to send a transfer + Value: big.NewInt(int64(sendAmount)), + Nonce: uint64(i), + } + sendTransaction(t, client, sender, txn) + }(i, receivers[i]) + } + + wg.Wait() + + err = cluster.WaitUntil(2*time.Minute, func() bool { + for _, receiver := range receivers { + balance, err := client.GetBalance(receiver, ethgo.Latest) + if err != nil { + return true + } + t.Logf("Balance %s %s", receiver, balance) + if balance.Uint64() != uint64(sendAmount) { + return false + } + } + + return true + }) + require.NoError(t, err) +} + +// First account send some amount to second one and then second one to third account +func TestE2E_TxPool_Transfer_Linear(t *testing.T) { + premine, err := wallet.GenerateKey() + require.NoError(t, err) + + // first account should have some matics premined + cluster := framework.NewTestCluster(t, 5, framework.WithPremine(types.Address(premine.Address()))) + defer cluster.Stop() + + require.NoError(t, cluster.WaitForBlock(2, 1*time.Minute)) + + client := cluster.Servers[0].JSONRPC().Eth() + + // estimate gas price + gasPrice, err := client.GasPrice() + require.NoError(t, err) + + waitUntilBalancesChanged := func(acct ethgo.Address) error { + err := cluster.WaitUntil(30*time.Second, func() bool { + balance, err := client.GetBalance(acct, ethgo.Latest) + if err != nil { + return true + } + + return balance.Cmp(big.NewInt(0)) > 0 + }) + + return err + } + + num := 4 + receivers := []*wallet.Key{ + premine, + } + + for i := 0; i < num-1; i++ { + key, err := wallet.GenerateKey() + assert.NoError(t, err) + + receivers = append(receivers, key) + } + + // Gas cost is always the same since value transfers are deterministic (21k gas). + // Then, the total gas cost required to make a transfer is 21k multiplied by + // the selected gas price. + gasCost := int(21000 * gasPrice) + sendAmount := 3000000 + + // We are going to fund the accounts in linear fashion: + // A (premined account) -> B -> C -> D -> E + // At the end, all of them (except the premined account) will have the same `sendAmount` + // of balance. + for i := 1; i < num; i++ { + // we have to send enough value to account `i` so that it has enough to fund + // its child i+1 (cover costs + send amounts). + // This means that since gasCost and sendAmount are fixed, account C must receive gasCost * 2 + // (to cover two more transfers C->D and D->E) + sendAmount * 3 (one bundle for each C,D and E). + amount := gasCost*(num-i-1) + sendAmount*(num-i) + recipient := receivers[i].Address() + txn := ðgo.Transaction{ + Value: big.NewInt(int64(amount)), + To: &recipient, + GasPrice: gasPrice, + Gas: 21000, + Nonce: 0, + } + sendTransaction(t, client, receivers[i-1], txn) + + err := waitUntilBalancesChanged(receivers[i].Address()) + require.NoError(t, err) + } + + for i := 1; i < num; i++ { + balance, err := client.GetBalance(receivers[i].Address(), ethgo.Latest) + require.NoError(t, err) + require.Equal(t, uint64(sendAmount), balance.Uint64()) + } +} + +func TestE2E_TxPool_TransactionWithHeaderInstuctions(t *testing.T) { + sidechainKey, err := wallet.GenerateKey() + require.NoError(t, err) + + cluster := framework.NewTestCluster(t, 4, + framework.WithPremine(types.Address(sidechainKey.Address())), + ) + defer cluster.Stop() + + require.NoError(t, cluster.WaitForBlock(1, 20*time.Second)) + + relayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(cluster.Servers[0].JSONRPCAddr())) + require.NoError(t, err) + + receipt, err := relayer.SendTransaction(ðgo.Transaction{Input: contractsapi.TestWriteBlockMetadata.Bytecode}, sidechainKey) + require.NoError(t, err) + require.Equal(t, uint64(types.ReceiptSuccess), receipt.Status) + + receipt, err = ABITransaction(relayer, sidechainKey, contractsapi.TestWriteBlockMetadata, receipt.ContractAddress, "init", []interface{}{}) + require.NoError(t, err) + require.Equal(t, uint64(types.ReceiptSuccess), receipt.Status) + + require.NoError(t, cluster.WaitForBlock(10, 1*time.Minute)) +} + +// sendTransaction is a helper function which signs transaction with provided private key and sends it +func sendTransaction(t *testing.T, client *jsonrpc.Eth, sender *wallet.Key, txn *ethgo.Transaction) { + t.Helper() + + chainID, err := client.ChainID() + require.NoError(t, err) + + signer := wallet.NewEIP155Signer(chainID.Uint64()) + signedTxn, err := signer.SignTx(txn, sender) + require.NoError(t, err) + + txnRaw, err := signedTxn.MarshalRLPTo(nil) + require.NoError(t, err) + + _, err = client.SendRawTransaction(txnRaw) + require.NoError(t, err) +} diff --git a/e2e/framework/ibft.go b/e2e/framework/ibft.go index 2665ec80f4..61fa1d421b 100644 --- a/e2e/framework/ibft.go +++ b/e2e/framework/ibft.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/0xPolygon/polygon-edge/helper/common" "github.com/0xPolygon/polygon-edge/types" ) @@ -134,9 +135,9 @@ func (m *IBFTServersManager) GetServer(i int) *TestServer { func initLogsDir(t *testing.T) (string, error) { t.Helper() - logsDir := path.Join("..", "e2e-logs", fmt.Sprintf("e2e-logs-%d", startTime), t.Name()) + logsDir := path.Join("..", fmt.Sprintf("e2e-logs-%d", startTime), t.Name()) - if err := os.MkdirAll(logsDir, 0755); err != nil { + if err := common.CreateDirSafe(logsDir, 0755); err != nil { return "", err } diff --git a/e2e/framework/testserver.go b/e2e/framework/testserver.go index 027218ae76..717c3cc94d 100644 --- a/e2e/framework/testserver.go +++ b/e2e/framework/testserver.go @@ -195,6 +195,7 @@ func (t *TestServer) SecretsInit() (*InitIBFTResult, error) { commandSlice := strings.Split(fmt.Sprintf("secrets %s", secretsInitCmd.Use), " ") args = append(args, commandSlice...) args = append(args, "--data-dir", filepath.Join(t.Config.IBFTDir, "tmp")) + args = append(args, "--insecure") cmd := exec.Command(resolveBinary(), args...) //nolint:gosec cmd.Dir = t.Config.RootDir diff --git a/go.mod b/go.mod index 3079c0f0e0..eba912ed21 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/hashicorp/go-hclog v1.3.1 github.com/hashicorp/go-immutable-radix v1.3.1 github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/golang-lru v0.5.4 + github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/hashicorp/hcl v1.0.0 github.com/hashicorp/vault/api v1.8.2 github.com/libp2p/go-libp2p v0.22.0 @@ -29,7 +29,7 @@ require ( github.com/stretchr/testify v1.8.1 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/umbracle/fastrlp v0.0.0-20220527094140-59d5dd30e722 - github.com/umbracle/go-eth-bn256 v0.0.0-20190607160430-b36caf4e0f6b + github.com/umbracle/go-eth-bn256 v0.0.0-20230125114011-47cb310d9b0b golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e google.golang.org/grpc v1.50.1 google.golang.org/protobuf v1.28.1 @@ -48,46 +48,65 @@ require ( github.com/ipfs/go-cid v0.2.0 // indirect github.com/klauspost/compress v1.15.5 // indirect github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/umbracle/ethgo v0.1.4-0.20221117101647-b81ef2f07953 + github.com/mitchellh/mapstructure v1.5.0 + github.com/umbracle/ethgo v0.1.4-0.20230126112511-6a4d02533af6 github.com/valyala/fastjson v1.6.3 // indirect go.uber.org/zap v1.22.0 // indirect - golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect + golang.org/x/sys v0.4.0 // indirect golang.org/x/tools v0.1.12 // indirect google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e gopkg.in/yaml.v3 v3.0.1 lukechampine.com/blake3 v1.1.7 // indirect ) -require github.com/0xPolygon/go-ibft v0.2.0 +require ( + github.com/0xPolygon/go-ibft v0.3.0 + github.com/docker/docker v20.10.18+incompatible + github.com/docker/go-connections v0.4.0 + github.com/mitchellh/go-glint v0.0.0-20210722152315-6515ceb4a127 + go.etcd.io/bbolt v1.3.6 +) -require gopkg.in/DataDog/dd-trace-go.v1 v1.43.1 +require ( + github.com/dave/jennifer v1.6.0 + golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 + gopkg.in/DataDog/dd-trace-go.v1 v1.43.1 + pgregory.net/rapid v0.5.5 +) require ( cloud.google.com/go/compute v1.10.0 // indirect cloud.google.com/go/iam v0.5.0 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/DataDog/datadog-agent/pkg/obfuscate v0.0.0-20211129110424-6491aa3bf583 // indirect github.com/DataDog/datadog-go v4.8.2+incompatible // indirect github.com/DataDog/datadog-go/v5 v5.0.2 // indirect github.com/DataDog/gostackparse v0.5.0 // indirect github.com/DataDog/sketches-go v1.2.1 // indirect github.com/Microsoft/go-winio v0.5.1 // indirect + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/VividCortex/ewma v1.1.1 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect github.com/bwesterb/go-ristretto v1.2.0 // indirect + github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cheekybits/genny v1.0.0 // indirect + github.com/cheggaaa/pb/v3 v3.0.5 // indirect github.com/consensys/gnark-crypto v0.5.3 // indirect + github.com/containerd/console v1.0.1 // indirect + github.com/containerd/continuity v0.0.0-20191214063359-1097c8bae83b // indirect github.com/coreos/go-systemd/v22 v22.3.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect + github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/go-units v0.4.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/flynn/noise v1.0.0 // indirect @@ -103,6 +122,7 @@ require ( github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect github.com/googleapis/gax-go/v2 v2.6.0 // indirect + github.com/gookit/color v1.3.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-plugin v1.4.5 // indirect @@ -146,6 +166,7 @@ require ( github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-pointer v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect @@ -153,7 +174,10 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect + github.com/morikuni/aec v1.0.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base36 v0.1.0 // indirect github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect @@ -164,8 +188,12 @@ require ( github.com/multiformats/go-varint v0.0.6 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/oklog/run v1.1.0 // indirect + github.com/opencontainers/go-digest v1.0.0-rc1 // indirect + github.com/opencontainers/image-spec v1.0.1 // indirect + github.com/opencontainers/runc v0.1.1 // indirect github.com/opencontainers/runtime-spec v1.0.2 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/ory/dockertest v3.3.5+incompatible // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/philhofer/fwd v1.1.1 // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect @@ -176,10 +204,13 @@ require ( github.com/prometheus/procfs v0.8.0 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/tinylib/msgp v1.1.2 // indirect + github.com/tj/go-spin v1.1.0 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.37.0 // indirect @@ -192,7 +223,7 @@ require ( golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458 // indirect golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1 // indirect - golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.8 // indirect golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect @@ -200,5 +231,6 @@ require ( google.golang.org/appengine v1.6.7 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gotest.tools/v3 v3.0.2 // indirect inet.af/netaddr v0.0.0-20220617031823-097006376321 // indirect ) diff --git a/go.sum b/go.sum index ba12f610b1..91d337ef04 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -47,11 +48,10 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/0xPolygon/go-ibft v0.0.0-20220810095021-e43142f8d267 h1:+mFLx9IKW16fOcTKjZjkom3TGnihOuPwYAz2c6+UUWQ= -github.com/0xPolygon/go-ibft v0.0.0-20220810095021-e43142f8d267/go.mod h1:QPrugDXgsCFy2FeCJ0YokPrnyi1GoLhDj/PLO1dSoNY= -github.com/0xPolygon/go-ibft v0.2.0 h1:NoPUUEtBkMAk0p3gpjlaCpkm0eyM90W69OfaosWcRZM= -github.com/0xPolygon/go-ibft v0.2.0/go.mod h1:mJGwdcGvLdg9obtnzBqx1aAzuhzvGeWav5AiUWN7F3Q= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +github.com/0xPolygon/go-ibft v0.3.0 h1:4j8yaRgArErnZy1alQUHOSStD/ogAoEWVRn/CG0JlvI= +github.com/0xPolygon/go-ibft v0.3.0/go.mod h1:mJGwdcGvLdg9obtnzBqx1aAzuhzvGeWav5AiUWN7F3Q= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-agent/pkg/obfuscate v0.0.0-20211129110424-6491aa3bf583 h1:3nVO1nQyh64IUY6BPZUpMYMZ738Pu+LsMt3E0eqqIYw= @@ -69,6 +69,9 @@ github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpz github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= +github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -112,6 +115,7 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bwesterb/go-ristretto v1.2.0 h1:xxWOVbN5m8NNKiSDZXE1jtZvZnC6JSJ9cYFADiZcWtw= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -120,6 +124,8 @@ github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cb github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/cheggaaa/pb/v3 v3.0.5 h1:lmZOti7CraK9RSjzExsY53+WWfub9Qv13B5m4ptEoPE= +github.com/cheggaaa/pb/v3 v3.0.5/go.mod h1:X1L61/+36nz9bjIsrDU52qHKOQukUQe2Ge+YvGuquCw= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -136,7 +142,10 @@ github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaO github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= +github.com/containerd/console v1.0.1 h1:u7SFAJyRqWcG6ogaMAx3KjSTy1e3hT9QxqX7Jco7dRc= +github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/continuity v0.0.0-20191214063359-1097c8bae83b h1:pik3LX++5O3UiNWv45wfP/WT81l7ukBJzd3uUiifbSU= +github.com/containerd/continuity v0.0.0-20191214063359-1097c8bae83b/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= @@ -144,6 +153,9 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/dave/jennifer v1.6.0 h1:MQ/6emI2xM7wt0tJzJzyUik2Q3Tcn2eE0vtYgh4GPVI= +github.com/dave/jennifer v1.6.0/go.mod h1:AxTG893FiZKqxy3FP1kL80VMshSMuz2G+EgvszgGRnk= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -157,9 +169,15 @@ github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/Lu github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v20.10.18+incompatible h1:SN84VYXTBNGn92T/QwIRPlum9zfemfitN7pbsp26WSc= +github.com/docker/docker v20.10.18+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= @@ -292,9 +310,12 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.6.0 h1:SXk3ABtQYDT/OH8jAyvEOQ58mgawq5C4o/4/89qN2ZU= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/gookit/color v1.3.1 h1:PPD/C7sf8u2L8XQPdPgsWRoAiLQGZEZOzU3cf5IYYUk= +github.com/gookit/color v1.3.1/go.mod h1:R3ogXq2B9rTbXoSHJ1HyUVAZ3poOJHpd9nQmyGZsfvQ= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -336,8 +357,8 @@ github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0t github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= +github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/vault/api v1.8.2 h1:C7OL9YtOtwQbTKI9ogB0A1wffRbCN+rH/LLCHO3d8HM= @@ -353,6 +374,7 @@ github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixH github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/ipfs/go-cid v0.2.0 h1:01JTiihFq9en9Vz0lc0VDWvZe/uBonGpzo4THP0vcQ0= @@ -416,6 +438,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= @@ -464,16 +487,21 @@ github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbd github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= @@ -493,21 +521,29 @@ github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-glint v0.0.0-20210722152315-6515ceb4a127 h1:/EdGXMUGYFdp0+cmGjVLl/Qbx3G10csqgj22ZkrPFEA= +github.com/mitchellh/go-glint v0.0.0-20210722152315-6515ceb4a127/go.mod h1:9X3rpO+I3yuihb6p8ktF8qWxROGwij9DBW/czUsMlhk= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= @@ -549,6 +585,7 @@ github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= @@ -556,19 +593,24 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= @@ -578,6 +620,7 @@ github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -650,19 +693,24 @@ github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go. github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -686,15 +734,17 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45 github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tj/go-spin v1.1.0 h1:lhdWZsvImxvZ3q1C5OIB7d72DuOwP4O2NdBg9PyzNds= +github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= -github.com/umbracle/ethgo v0.1.4-0.20221117101647-b81ef2f07953 h1:ep+lwZyyeh1iw8UojTxslDsqPw0305Vu6lOiEebWS8k= -github.com/umbracle/ethgo v0.1.4-0.20221117101647-b81ef2f07953/go.mod h1:8QIHEG/YfGnW4I5AND2Znl9W0LU3tXR9IGqgmSieiGo= +github.com/umbracle/ethgo v0.1.4-0.20230126112511-6a4d02533af6 h1:WqlyYNdrBECgDwDIEMxa4mLUSH/FfPdAuOnniUqNpJs= +github.com/umbracle/ethgo v0.1.4-0.20230126112511-6a4d02533af6/go.mod h1:8QIHEG/YfGnW4I5AND2Znl9W0LU3tXR9IGqgmSieiGo= github.com/umbracle/fastrlp v0.0.0-20220527094140-59d5dd30e722 h1:10Nbw6cACsnQm7r34zlpJky+IzxVLRk6MKTS2d3Vp0E= github.com/umbracle/fastrlp v0.0.0-20220527094140-59d5dd30e722/go.mod h1:c8J0h9aULj2i3umrfyestM6jCq0LK0U6ly6bWy96nd4= -github.com/umbracle/go-eth-bn256 v0.0.0-20190607160430-b36caf4e0f6b h1:t3nz9xXkLZJz+ZlTGFT3ixsCGO5AHx1Yift2EAfjnnc= -github.com/umbracle/go-eth-bn256 v0.0.0-20190607160430-b36caf4e0f6b/go.mod h1:B2zj4f3YmUPeyCNSlAEgOf6tuGzeYKvIxAZzwy9PxPA= +github.com/umbracle/go-eth-bn256 v0.0.0-20230125114011-47cb310d9b0b h1:5/xofhZiOG0I9DQXqDSPxqYObk6QI7mBGMJI+ngyIgc= +github.com/umbracle/go-eth-bn256 v0.0.0-20230125114011-47cb310d9b0b/go.mod h1:H8SeC2PWEciymT92Mt07Qcfjr2FMEuCz/V+KPtPTy+U= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= @@ -712,6 +762,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -725,7 +777,7 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= @@ -743,6 +795,7 @@ go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvo go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -755,6 +808,7 @@ golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -877,6 +931,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -916,6 +971,8 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -929,6 +986,7 @@ golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -938,10 +996,12 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -972,6 +1032,7 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1119,6 +1180,7 @@ google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/DataDog/dd-trace-go.v1 v1.43.1 h1:Dez4VzRQWAI5YXJRBx58BiC0gONGuW/oY4l8fWKzOXY= gopkg.in/DataDog/dd-trace-go.v1 v1.43.1/go.mod h1:YL9g+nlUY7ByCffD5pDytAqy99GNbytRV0EBpKuldM4= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1127,6 +1189,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= @@ -1146,6 +1209,9 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1159,6 +1225,8 @@ inet.af/netaddr v0.0.0-20220617031823-097006376321 h1:B4dC8ySKTQXasnjDTMsoCMf1sQ inet.af/netaddr v0.0.0-20220617031823-097006376321/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k= lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= +pgregory.net/rapid v0.5.5 h1:jkgx1TjbQPD/feRoK+S/mXw9e1uj6WilpHrXJowi6oA= +pgregory.net/rapid v0.5.5/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/helper/common/common.go b/helper/common/common.go index d60f620efc..b886dfa757 100644 --- a/helper/common/common.go +++ b/helper/common/common.go @@ -4,10 +4,14 @@ import ( "encoding/json" "errors" "fmt" + "io/fs" "math" + "math/big" "os" "os/signal" + "os/user" "path/filepath" + "strconv" "syscall" "github.com/0xPolygon/polygon-edge/helper/hex" @@ -67,14 +71,14 @@ func ToFixedFloat(num float64, precision int) float64 { } // SetupDataDir sets up the data directory and the corresponding sub-directories -func SetupDataDir(dataDir string, paths []string) error { - if err := createDir(dataDir); err != nil { +func SetupDataDir(dataDir string, paths []string, perms fs.FileMode) error { + if err := CreateDirSafe(dataDir, perms); err != nil { return fmt.Errorf("failed to create data dir: (%s): %w", dataDir, err) } for _, path := range paths { path := filepath.Join(dataDir, path) - if err := createDir(path); err != nil { + if err := CreateDirSafe(path, perms); err != nil { return fmt.Errorf("failed to create path: (%s): %w", path, err) } } @@ -84,6 +88,11 @@ func SetupDataDir(dataDir string, paths []string) error { // DirectoryExists checks if the directory at the specified path exists func DirectoryExists(directoryPath string) bool { + // Check if path is empty + if directoryPath == "" { + return false + } + // Grab the absolute filepath pathAbs, err := filepath.Abs(directoryPath) if err != nil { @@ -98,17 +107,101 @@ func DirectoryExists(directoryPath string) bool { return true } -// createDir creates a file system directory if it doesn't exist -func createDir(path string) error { - _, err := os.Stat(path) +// Checks if the file at the specified path exists +func FileExists(filePath string) bool { + // Check if path is empty + if filePath == "" { + return false + } + + // Grab the absolute filepath + pathAbs, err := filepath.Abs(filePath) + if err != nil { + return false + } + + // Check if the file exists, and that it's actually a file if there is a hit + if fileInfo, statErr := os.Stat(pathAbs); os.IsNotExist(statErr) || (fileInfo != nil && fileInfo.IsDir()) { + return false + } + + return true +} + +// Creates a directory at path and with perms level permissions. +// If directory already exists, owner and permissions are verified. +func CreateDirSafe(path string, perms fs.FileMode) error { + info, err := os.Stat(path) + // check if an error occurred other than path not exists if err != nil && !os.IsNotExist(err) { return err } - if os.IsNotExist(err) { - if err := os.MkdirAll(path, os.ModePerm); err != nil { + // create directory if it does not exist + if !DirectoryExists(path) { + if err := os.MkdirAll(path, perms); err != nil { return err } + + return nil + } + + // verify that existing directory's owner and permissions are safe + return verifyFileOwnerAndPermissions(path, info, perms) +} + +// Creates a file at path and with perms level permissions. +// If file already exists, owner and permissions are +// verified, and the file is overwritten. +func SaveFileSafe(path string, data []byte, perms fs.FileMode) error { + info, err := os.Stat(path) + // check if an error occurred other than path not exists + if err != nil && !os.IsNotExist(err) { + return err + } + + if FileExists(path) { + // verify that existing file's owner and permissions are safe + if err := verifyFileOwnerAndPermissions(path, info, perms); err != nil { + return err + } + } + + // create or overwrite the file + return os.WriteFile(path, data, perms) +} + +// Verifies that the file owner is the current user, +// or the file owner is in the same group as current user +// and permissions are set correctly by the owner. +func verifyFileOwnerAndPermissions(path string, info fs.FileInfo, expectedPerms fs.FileMode) error { + // get stats + stat, ok := info.Sys().(*syscall.Stat_t) + if stat == nil || !ok { + return fmt.Errorf("failed to get stats of %s", path) + } + + // get current user + currUser, err := user.Current() + if err != nil { + return fmt.Errorf("failed to get current user") + } + + // get user id of the owner + ownerUID := strconv.FormatUint(uint64(stat.Uid), 10) + if currUser.Uid == ownerUID { + return nil + } + + // get group id of the owner + ownerGID := strconv.FormatUint(uint64(stat.Gid), 10) + if currUser.Gid != ownerGID { + return fmt.Errorf("file/directory created by a user from a different group: %s", path) + } + + // check if permissions are set correctly by the owner + if info.Mode() != expectedPerms { + return fmt.Errorf("permissions of the file/directory '%s' are set incorrectly by another user", path) } return nil @@ -174,3 +267,24 @@ func PadLeftOrTrim(bb []byte, size int) []byte { return tmp } + +// ExtendByteSlice extends given byte slice by needLength parameter and trims it +func ExtendByteSlice(b []byte, needLength int) []byte { + b = b[:cap(b)] + + if n := needLength - len(b); n > 0 { + b = append(b, make([]byte, n)...) + } + + return b[:needLength] +} + +// BigIntDivCeil performs integer division and rounds given result to next bigger integer number +// It is calculated using this formula result = (a + b - 1) / b +func BigIntDivCeil(a, b *big.Int) *big.Int { + result := new(big.Int) + + return result.Add(a, b). + Sub(result, big.NewInt(1)). + Div(result, b) +} diff --git a/helper/common/common_test.go b/helper/common/common_test.go new file mode 100644 index 0000000000..ff009409c5 --- /dev/null +++ b/helper/common/common_test.go @@ -0,0 +1,62 @@ +package common + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_ExtendByteSlice(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + length int + newLength int + }{ + {"With trimming", 4, 2}, + {"Without trimming", 4, 8}, + {"Without trimming (same lengths)", 4, 4}, + } + + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + originalSlice := make([]byte, c.length) + for i := 0; i < c.length; i++ { + originalSlice[i] = byte(i * 2) + } + + newSlice := ExtendByteSlice(originalSlice, c.newLength) + require.Len(t, newSlice, c.newLength) + if c.length > c.newLength { + require.Equal(t, originalSlice[:c.newLength], newSlice) + } else { + require.Equal(t, originalSlice, newSlice[:c.length]) + } + }) + } +} + +func Test_BigIntDivCeil(t *testing.T) { + t.Parallel() + + cases := []struct { + a int64 + b int64 + result int64 + }{ + {a: 10, b: 3, result: 4}, + {a: 13, b: 6, result: 3}, + {a: -1, b: 3, result: 0}, + {a: -20, b: 3, result: -6}, + {a: -15, b: 3, result: -5}, + } + + for _, c := range cases { + require.Equal(t, c.result, BigIntDivCeil(big.NewInt(c.a), big.NewInt(c.b)).Int64()) + } +} diff --git a/helper/ipc/ipc_unix.go b/helper/ipc/ipc_unix.go index b85f28390a..ec9023d26c 100644 --- a/helper/ipc/ipc_unix.go +++ b/helper/ipc/ipc_unix.go @@ -8,6 +8,8 @@ import ( "os" "path/filepath" "time" + + "github.com/0xPolygon/polygon-edge/helper/common" ) // Dial dials an IPC path @@ -22,7 +24,7 @@ func DialTimeout(path string, timeout time.Duration) (net.Conn, error) { // Listen listens an IPC path func Listen(path string) (net.Listener, error) { - if err := os.MkdirAll(filepath.Dir(path), 0751); err != nil { + if err := common.CreateDirSafe(filepath.Dir(path), 0751); err != nil { return nil, err } diff --git a/helper/keystore/keystore.go b/helper/keystore/keystore.go index 09ffb8dfd7..947b2620e3 100644 --- a/helper/keystore/keystore.go +++ b/helper/keystore/keystore.go @@ -4,6 +4,8 @@ import ( "encoding/hex" "fmt" "os" + + "github.com/0xPolygon/polygon-edge/helper/common" ) type createFn func() ([]byte, error) @@ -35,7 +37,7 @@ func CreateIfNotExists(path string, create createFn) ([]byte, error) { // Encode it to a readable format (Base64) and write to disk keyBuff = []byte(hex.EncodeToString(keyBuff)) - if err = os.WriteFile(path, keyBuff, os.ModePerm); err != nil { + if err = common.SaveFileSafe(path, keyBuff, 0440); err != nil { return nil, fmt.Errorf("unable to write private key to disk (%s), %w", path, err) } diff --git a/jsonrpc/bridge_endpoint.go b/jsonrpc/bridge_endpoint.go new file mode 100644 index 0000000000..d7b4759a59 --- /dev/null +++ b/jsonrpc/bridge_endpoint.go @@ -0,0 +1,26 @@ +package jsonrpc + +import ( + "github.com/0xPolygon/polygon-edge/types" +) + +// bridgeStore interface provides access to the methods needed by bridge endpoint +type bridgeStore interface { + GenerateExitProof(exitID, epoch, checkpointBlock uint64) (types.Proof, error) + GetStateSyncProof(stateSyncID uint64) (types.Proof, error) +} + +// Bridge is the bridge jsonrpc endpoint +type Bridge struct { + store bridgeStore +} + +// GenerateExitProof generates exit proof for given exit event +func (b *Bridge) GenerateExitProof(exitID, epoch, checkpointBlock argUint64) (interface{}, error) { + return b.store.GenerateExitProof(uint64(exitID), uint64(epoch), uint64(checkpointBlock)) +} + +// GetStateSyncProof retrieves the StateSync proof +func (b *Bridge) GetStateSyncProof(stateSyncID argUint64) (interface{}, error) { + return b.store.GetStateSyncProof(uint64(stateSyncID)) +} diff --git a/jsonrpc/bridge_endpoint_test.go b/jsonrpc/bridge_endpoint_test.go new file mode 100644 index 0000000000..4e43e2d007 --- /dev/null +++ b/jsonrpc/bridge_endpoint_test.go @@ -0,0 +1,54 @@ +package jsonrpc + +import ( + "encoding/json" + "testing" + + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/require" +) + +func TestBridgeEndpoint(t *testing.T) { + store := newMockStore() + + dispatcher := newDispatcher( + hclog.NewNullLogger(), + store, + &dispatcherParams{ + chainID: 0, + priceLimit: 0, + jsonRPCBatchLengthLimit: 20, + blockRangeLimit: 1000, + }, + ) + + mockConnection, _ := newMockWsConnWithMsgCh() + + msg := []byte(`{ + "method": "bridge_generateExitProof", + "params": ["0x0001", "0x0001", "0x0002"], + "id": 1 + }`) + + data, err := dispatcher.HandleWs(msg, mockConnection) + require.NoError(t, err) + + resp := new(SuccessResponse) + require.NoError(t, json.Unmarshal(data, resp)) + require.Nil(t, resp.Error) + require.NotNil(t, resp.Result) + + msg = []byte(`{ + "method": "bridge_getStateSyncProof", + "params": ["0x1"], + "id": 1 + }`) + + data, err = dispatcher.HandleWs(msg, mockConnection) + require.NoError(t, err) + + resp = new(SuccessResponse) + require.NoError(t, json.Unmarshal(data, resp)) + require.Nil(t, resp.Error) + require.NotNil(t, resp.Result) +} diff --git a/jsonrpc/dispatcher.go b/jsonrpc/dispatcher.go index 5a536d4ff9..8423786cb1 100644 --- a/jsonrpc/dispatcher.go +++ b/jsonrpc/dispatcher.go @@ -34,6 +34,7 @@ type endpoints struct { Web3 *Web3 Net *Net TxPool *TxPool + Bridge *Bridge Debug *Debug } @@ -96,6 +97,9 @@ func (d *Dispatcher) registerEndpoints(store JSONRPCStore) { d.endpoints.TxPool = &TxPool{ store, } + d.endpoints.Bridge = &Bridge{ + store, + } d.endpoints.Debug = &Debug{ store, } @@ -104,6 +108,7 @@ func (d *Dispatcher) registerEndpoints(store JSONRPCStore) { d.registerService("net", d.endpoints.Net) d.registerService("web3", d.endpoints.Web3) d.registerService("txpool", d.endpoints.TxPool) + d.registerService("bridge", d.endpoints.Bridge) d.registerService("debug", d.endpoints.Debug) } diff --git a/jsonrpc/jsonrpc.go b/jsonrpc/jsonrpc.go index 05206be16f..4737c60c7a 100644 --- a/jsonrpc/jsonrpc.go +++ b/jsonrpc/jsonrpc.go @@ -55,6 +55,7 @@ type JSONRPCStore interface { networkStore txPoolStore filterManagerStore + bridgeStore debugStore } diff --git a/jsonrpc/mocks_test.go b/jsonrpc/mocks_test.go index 3fa7b97446..8650ac292f 100644 --- a/jsonrpc/mocks_test.go +++ b/jsonrpc/mocks_test.go @@ -172,6 +172,27 @@ func (m *mockStore) GetCapacity() (uint64, uint64) { return 0, 0 } +func (m *mockStore) GenerateExitProof(exitID, epoch, checkpointNumber uint64) (types.Proof, error) { + hash := types.BytesToHash([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) + + return types.Proof{ + Data: []types.Hash{hash}, + Metadata: map[string]interface{}{ + "LeafIndex": 1111111111111, + }, + }, nil +} + func (m *mockStore) GetPeers() int { return 20 } + +func (m *mockStore) GetStateSyncProof(stateSyncID uint64) (types.Proof, error) { + hash := types.BytesToHash([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) + ssp := types.Proof{ + Data: []types.Hash{hash}, + Metadata: map[string]interface{}{}, + } + + return ssp, nil +} diff --git a/network/e2e_testing.go b/network/e2e_testing.go index 2d5ec04b07..81f6db9338 100644 --- a/network/e2e_testing.go +++ b/network/e2e_testing.go @@ -373,7 +373,7 @@ func GenerateTestLibp2pKey(t *testing.T) (crypto.PrivKey, string) { assert.NoError(t, err) // Instantiate the correct folder structure - setupErr := common.SetupDataDir(dir, []string{"libp2p"}) + setupErr := common.SetupDataDir(dir, []string{"libp2p"}, 0770) if setupErr != nil { t.Fatalf("unable to generate libp2p folder structure, %v", setupErr) } diff --git a/network/proto/discovery.pb.go b/network/proto/discovery.pb.go index 0459ebcce4..dd645d5469 100644 --- a/network/proto/discovery.pb.go +++ b/network/proto/discovery.pb.go @@ -1,13 +1,12 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.25.0 -// protoc v3.12.0 +// protoc-gen-go v1.28.1 +// protoc v3.21.7 // source: network/proto/discovery.proto package proto import ( - proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -21,10 +20,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// This is a compile-time assertion that a sufficiently up-to-date version -// of the legacy proto package is being used. -const _ = proto.ProtoPackageIsVersion4 - type FindPeersReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache diff --git a/network/proto/discovery_grpc.pb.go b/network/proto/discovery_grpc.pb.go index de7de8bb32..e9861027bd 100644 --- a/network/proto/discovery_grpc.pb.go +++ b/network/proto/discovery_grpc.pb.go @@ -1,4 +1,8 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.21.7 +// source: network/proto/discovery.proto package proto diff --git a/network/proto/identity.pb.go b/network/proto/identity.pb.go index a15903149a..0447bb613f 100644 --- a/network/proto/identity.pb.go +++ b/network/proto/identity.pb.go @@ -1,8 +1,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 -// protoc v3.19.4 -// source: identity.proto +// protoc-gen-go v1.28.1 +// protoc v3.21.7 +// source: network/proto/identity.proto package proto @@ -35,7 +35,7 @@ type Status struct { func (x *Status) Reset() { *x = Status{} if protoimpl.UnsafeEnabled { - mi := &file_identity_proto_msgTypes[0] + mi := &file_network_proto_identity_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -48,7 +48,7 @@ func (x *Status) String() string { func (*Status) ProtoMessage() {} func (x *Status) ProtoReflect() protoreflect.Message { - mi := &file_identity_proto_msgTypes[0] + mi := &file_network_proto_identity_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -61,7 +61,7 @@ func (x *Status) ProtoReflect() protoreflect.Message { // Deprecated: Use Status.ProtoReflect.Descriptor instead. func (*Status) Descriptor() ([]byte, []int) { - return file_identity_proto_rawDescGZIP(), []int{0} + return file_network_proto_identity_proto_rawDescGZIP(), []int{0} } func (x *Status) GetMetadata() map[string]string { @@ -111,7 +111,7 @@ type Status_Key struct { func (x *Status_Key) Reset() { *x = Status_Key{} if protoimpl.UnsafeEnabled { - mi := &file_identity_proto_msgTypes[2] + mi := &file_network_proto_identity_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -124,7 +124,7 @@ func (x *Status_Key) String() string { func (*Status_Key) ProtoMessage() {} func (x *Status_Key) ProtoReflect() protoreflect.Message { - mi := &file_identity_proto_msgTypes[2] + mi := &file_network_proto_identity_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -137,7 +137,7 @@ func (x *Status_Key) ProtoReflect() protoreflect.Message { // Deprecated: Use Status_Key.ProtoReflect.Descriptor instead. func (*Status_Key) Descriptor() ([]byte, []int) { - return file_identity_proto_rawDescGZIP(), []int{0, 1} + return file_network_proto_identity_proto_rawDescGZIP(), []int{0, 1} } func (x *Status_Key) GetSignature() string { @@ -154,56 +154,56 @@ func (x *Status_Key) GetMessage() string { return "" } -var File_identity_proto protoreflect.FileDescriptor +var File_network_proto_identity_proto protoreflect.FileDescriptor -var file_identity_proto_rawDesc = []byte{ - 0x0a, 0x0e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x12, 0x02, 0x76, 0x31, 0x22, 0xb4, 0x02, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x34, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, - 0x4b, 0x65, 0x79, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x61, - 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, - 0x18, 0x0a, 0x07, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x74, 0x65, 0x6d, - 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x44, 0x69, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0d, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x44, 0x69, 0x61, 0x6c, 0x1a, - 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3d, 0x0a, 0x03, - 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x2b, 0x0a, 0x08, 0x49, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1f, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, - 0x12, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x0a, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x10, 0x5a, 0x0e, 0x2f, 0x6e, 0x65, 0x74, - 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, +var file_network_proto_identity_proto_rawDesc = []byte{ + 0x0a, 0x1c, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, + 0x76, 0x31, 0x22, 0xb4, 0x02, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x34, 0x0a, + 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4b, 0x65, + 0x79, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x18, 0x0a, + 0x07, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x74, 0x65, 0x6d, 0x70, 0x6f, + 0x72, 0x61, 0x72, 0x79, 0x44, 0x69, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, + 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x44, 0x69, 0x61, 0x6c, 0x1a, 0x3b, 0x0a, + 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3d, 0x0a, 0x03, 0x4b, 0x65, + 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x2b, 0x0a, 0x08, 0x49, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1f, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x0a, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x10, 0x5a, 0x0e, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, + 0x72, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( - file_identity_proto_rawDescOnce sync.Once - file_identity_proto_rawDescData = file_identity_proto_rawDesc + file_network_proto_identity_proto_rawDescOnce sync.Once + file_network_proto_identity_proto_rawDescData = file_network_proto_identity_proto_rawDesc ) -func file_identity_proto_rawDescGZIP() []byte { - file_identity_proto_rawDescOnce.Do(func() { - file_identity_proto_rawDescData = protoimpl.X.CompressGZIP(file_identity_proto_rawDescData) +func file_network_proto_identity_proto_rawDescGZIP() []byte { + file_network_proto_identity_proto_rawDescOnce.Do(func() { + file_network_proto_identity_proto_rawDescData = protoimpl.X.CompressGZIP(file_network_proto_identity_proto_rawDescData) }) - return file_identity_proto_rawDescData + return file_network_proto_identity_proto_rawDescData } -var file_identity_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_identity_proto_goTypes = []interface{}{ +var file_network_proto_identity_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_network_proto_identity_proto_goTypes = []interface{}{ (*Status)(nil), // 0: v1.Status nil, // 1: v1.Status.MetadataEntry (*Status_Key)(nil), // 2: v1.Status.Key } -var file_identity_proto_depIdxs = []int32{ +var file_network_proto_identity_proto_depIdxs = []int32{ 1, // 0: v1.Status.metadata:type_name -> v1.Status.MetadataEntry 2, // 1: v1.Status.keys:type_name -> v1.Status.Key 0, // 2: v1.Identity.Hello:input_type -> v1.Status @@ -215,13 +215,13 @@ var file_identity_proto_depIdxs = []int32{ 0, // [0:2] is the sub-list for field type_name } -func init() { file_identity_proto_init() } -func file_identity_proto_init() { - if File_identity_proto != nil { +func init() { file_network_proto_identity_proto_init() } +func file_network_proto_identity_proto_init() { + if File_network_proto_identity_proto != nil { return } if !protoimpl.UnsafeEnabled { - file_identity_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_network_proto_identity_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Status); i { case 0: return &v.state @@ -233,7 +233,7 @@ func file_identity_proto_init() { return nil } } - file_identity_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_network_proto_identity_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Status_Key); i { case 0: return &v.state @@ -250,18 +250,18 @@ func file_identity_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_identity_proto_rawDesc, + RawDescriptor: file_network_proto_identity_proto_rawDesc, NumEnums: 0, NumMessages: 3, NumExtensions: 0, NumServices: 1, }, - GoTypes: file_identity_proto_goTypes, - DependencyIndexes: file_identity_proto_depIdxs, - MessageInfos: file_identity_proto_msgTypes, + GoTypes: file_network_proto_identity_proto_goTypes, + DependencyIndexes: file_network_proto_identity_proto_depIdxs, + MessageInfos: file_network_proto_identity_proto_msgTypes, }.Build() - File_identity_proto = out.File - file_identity_proto_rawDesc = nil - file_identity_proto_goTypes = nil - file_identity_proto_depIdxs = nil + File_network_proto_identity_proto = out.File + file_network_proto_identity_proto_rawDesc = nil + file_network_proto_identity_proto_goTypes = nil + file_network_proto_identity_proto_depIdxs = nil } diff --git a/network/proto/identity_grpc.pb.go b/network/proto/identity_grpc.pb.go index 1bb5eb1088..0c881c6d76 100644 --- a/network/proto/identity_grpc.pb.go +++ b/network/proto/identity_grpc.pb.go @@ -1,4 +1,8 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.21.7 +// source: network/proto/identity.proto package proto @@ -97,5 +101,5 @@ var Identity_ServiceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: "identity.proto", + Metadata: "network/proto/identity.proto", } diff --git a/network/proto/testing.pb.go b/network/proto/testing.pb.go index ee68a80996..0ee0d1c8d9 100644 --- a/network/proto/testing.pb.go +++ b/network/proto/testing.pb.go @@ -1,8 +1,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 -// protoc v3.19.1 -// source: testing.proto +// protoc-gen-go v1.28.1 +// protoc v3.21.7 +// source: network/proto/testing.proto package proto @@ -31,7 +31,7 @@ type GenericMessage struct { func (x *GenericMessage) Reset() { *x = GenericMessage{} if protoimpl.UnsafeEnabled { - mi := &file_testing_proto_msgTypes[0] + mi := &file_network_proto_testing_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -44,7 +44,7 @@ func (x *GenericMessage) String() string { func (*GenericMessage) ProtoMessage() {} func (x *GenericMessage) ProtoReflect() protoreflect.Message { - mi := &file_testing_proto_msgTypes[0] + mi := &file_network_proto_testing_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -57,7 +57,7 @@ func (x *GenericMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use GenericMessage.ProtoReflect.Descriptor instead. func (*GenericMessage) Descriptor() ([]byte, []int) { - return file_testing_proto_rawDescGZIP(), []int{0} + return file_network_proto_testing_proto_rawDescGZIP(), []int{0} } func (x *GenericMessage) GetMessage() string { @@ -79,7 +79,7 @@ type ChattyRequest struct { func (x *ChattyRequest) Reset() { *x = ChattyRequest{} if protoimpl.UnsafeEnabled { - mi := &file_testing_proto_msgTypes[1] + mi := &file_network_proto_testing_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -92,7 +92,7 @@ func (x *ChattyRequest) String() string { func (*ChattyRequest) ProtoMessage() {} func (x *ChattyRequest) ProtoReflect() protoreflect.Message { - mi := &file_testing_proto_msgTypes[1] + mi := &file_network_proto_testing_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -105,7 +105,7 @@ func (x *ChattyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChattyRequest.ProtoReflect.Descriptor instead. func (*ChattyRequest) Descriptor() ([]byte, []int) { - return file_testing_proto_rawDescGZIP(), []int{1} + return file_network_proto_testing_proto_rawDescGZIP(), []int{1} } func (x *ChattyRequest) GetMessage() string { @@ -122,55 +122,56 @@ func (x *ChattyRequest) GetCount() int32 { return 0 } -var File_testing_proto protoreflect.FileDescriptor - -var file_testing_proto_rawDesc = []byte{ - 0x0a, 0x0d, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, - 0x02, 0x76, 0x31, 0x22, 0x2a, 0x0a, 0x0e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, - 0x3f, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x74, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x32, 0xf7, 0x01, 0x0a, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x32, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x12, 0x2e, 0x76, +var File_network_proto_testing_proto protoreflect.FileDescriptor + +var file_network_proto_testing_proto_rawDesc = []byte{ + 0x0a, 0x1b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x76, + 0x31, 0x22, 0x2a, 0x0a, 0x0e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x3f, 0x0a, + 0x0d, 0x43, 0x68, 0x61, 0x74, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, + 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x32, 0xf7, + 0x01, 0x0a, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x32, + 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x12, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x12, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x12, 0x3a, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x43, 0x68, 0x61, 0x74, 0x74, 0x79, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x74, 0x74, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x6e, 0x65, 0x72, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x30, 0x01, 0x12, 0x3b, + 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x43, 0x68, 0x61, 0x74, 0x74, 0x79, 0x43, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x12, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, + 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x28, 0x01, 0x12, 0x3b, 0x0a, 0x0d, 0x47, + 0x65, 0x74, 0x43, 0x68, 0x61, 0x74, 0x74, 0x79, 0x42, 0x69, 0x64, 0x69, 0x12, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x12, 0x3a, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x43, 0x68, 0x61, 0x74, 0x74, - 0x79, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, - 0x74, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x30, 0x01, - 0x12, 0x3b, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x43, 0x68, 0x61, 0x74, 0x74, 0x79, 0x43, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x12, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, - 0x65, 0x72, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x28, 0x01, 0x12, 0x3b, 0x0a, - 0x0d, 0x47, 0x65, 0x74, 0x43, 0x68, 0x61, 0x74, 0x74, 0x79, 0x42, 0x69, 0x64, 0x69, 0x12, 0x12, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x1a, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x10, 0x5a, 0x0e, 0x2f, 0x6e, - 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x61, 0x67, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x10, 0x5a, 0x0e, 0x2f, 0x6e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( - file_testing_proto_rawDescOnce sync.Once - file_testing_proto_rawDescData = file_testing_proto_rawDesc + file_network_proto_testing_proto_rawDescOnce sync.Once + file_network_proto_testing_proto_rawDescData = file_network_proto_testing_proto_rawDesc ) -func file_testing_proto_rawDescGZIP() []byte { - file_testing_proto_rawDescOnce.Do(func() { - file_testing_proto_rawDescData = protoimpl.X.CompressGZIP(file_testing_proto_rawDescData) +func file_network_proto_testing_proto_rawDescGZIP() []byte { + file_network_proto_testing_proto_rawDescOnce.Do(func() { + file_network_proto_testing_proto_rawDescData = protoimpl.X.CompressGZIP(file_network_proto_testing_proto_rawDescData) }) - return file_testing_proto_rawDescData + return file_network_proto_testing_proto_rawDescData } -var file_testing_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_testing_proto_goTypes = []interface{}{ +var file_network_proto_testing_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_network_proto_testing_proto_goTypes = []interface{}{ (*GenericMessage)(nil), // 0: v1.GenericMessage (*ChattyRequest)(nil), // 1: v1.ChattyRequest } -var file_testing_proto_depIdxs = []int32{ +var file_network_proto_testing_proto_depIdxs = []int32{ 0, // 0: v1.TestService.SayHello:input_type -> v1.GenericMessage 1, // 1: v1.TestService.GetChattyServer:input_type -> v1.ChattyRequest 0, // 2: v1.TestService.GetChattyClient:input_type -> v1.GenericMessage @@ -186,13 +187,13 @@ var file_testing_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for field type_name } -func init() { file_testing_proto_init() } -func file_testing_proto_init() { - if File_testing_proto != nil { +func init() { file_network_proto_testing_proto_init() } +func file_network_proto_testing_proto_init() { + if File_network_proto_testing_proto != nil { return } if !protoimpl.UnsafeEnabled { - file_testing_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_network_proto_testing_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GenericMessage); i { case 0: return &v.state @@ -204,7 +205,7 @@ func file_testing_proto_init() { return nil } } - file_testing_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_network_proto_testing_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ChattyRequest); i { case 0: return &v.state @@ -221,18 +222,18 @@ func file_testing_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_testing_proto_rawDesc, + RawDescriptor: file_network_proto_testing_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 1, }, - GoTypes: file_testing_proto_goTypes, - DependencyIndexes: file_testing_proto_depIdxs, - MessageInfos: file_testing_proto_msgTypes, + GoTypes: file_network_proto_testing_proto_goTypes, + DependencyIndexes: file_network_proto_testing_proto_depIdxs, + MessageInfos: file_network_proto_testing_proto_msgTypes, }.Build() - File_testing_proto = out.File - file_testing_proto_rawDesc = nil - file_testing_proto_goTypes = nil - file_testing_proto_depIdxs = nil + File_network_proto_testing_proto = out.File + file_network_proto_testing_proto_rawDesc = nil + file_network_proto_testing_proto_goTypes = nil + file_network_proto_testing_proto_depIdxs = nil } diff --git a/network/proto/testing_grpc.pb.go b/network/proto/testing_grpc.pb.go index 45a40b792d..afd7743793 100644 --- a/network/proto/testing_grpc.pb.go +++ b/network/proto/testing_grpc.pb.go @@ -1,4 +1,8 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.21.7 +// source: network/proto/testing.proto package proto @@ -299,5 +303,5 @@ var TestService_ServiceDesc = grpc.ServiceDesc{ ClientStreams: true, }, }, - Metadata: "testing.proto", + Metadata: "network/proto/testing.proto", } diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000000..bfda08cc3f --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,36 @@ +# Run servers form local binary + +## Prerequisites + +When deploying with `polybft` consensus, there are some additional dependencies: +* [npm](https://nodejs.org/en/) +* [go 1.18.x](https://go.dev/dl/) + +## Local development +Running `polygon-edge` from local binary can be done very easily by using provided `scripts` folder. + +* `scripts/cluster ibft` - deploy environment with `ibft` consensus +* `scripts/cluster polybft` - deploy environment with `polybft` consensus + +## Customisation +Use `scripts/cluster` script to customize chain parameters. +It already has some default parameters, which can be easily modified. +These are the `genesis` parameters from the official [docs](https://wiki.polygon.technology/docs/edge/get-started/cli-commands#genesis-flags). + +Primarily, the `--premine` parameter needs to be edited (`createGenesis` function) to include the accounts that the user has access to. + +## Considerations + +### Live console +The servers are run in foreground, meaning that the terminal console that is running the script +must remain active. To stop the servers - `Ctrl/Cmd + C`. +To interact with the chain use another terminal or run a dockerized environment by following the instructions +in `docker/README.md`. + +### Submodules +Before deploying `polybft` environment, `core-contracts` submodule needs to be downloaded. +To do that simply run `make download-submodules`. + +### Production +This is **NOT** a production ready deployment. It is to be used in *development* / *test* environments only. +For production usage, please check out the official [docs](https://wiki.polygon.technology/docs/edge/overview/). \ No newline at end of file diff --git a/scripts/cluster b/scripts/cluster new file mode 100755 index 0000000000..985607ec88 --- /dev/null +++ b/scripts/cluster @@ -0,0 +1,112 @@ +#!/usr/bin/env bash + +function initIbftConsensus() { + echo "Running with ibft consensus" + ./polygon-edge secrets init --insecure --data-dir test-chain- --num 4 + + node1_id=$(./polygon-edge secrets output --data-dir test-chain-1 | grep Node | head -n 1 | awk -F ' ' '{print $4}') + node2_id=$(./polygon-edge secrets output --data-dir test-chain-2 | grep Node | head -n 1 | awk -F ' ' '{print $4}') + + genesis_params="--consensus ibft --ibft-validators-prefix-path test-chain- \ + --bootnode /ip4/127.0.0.1/tcp/30301/p2p/$node1_id \ + --bootnode /ip4/127.0.0.1/tcp/30302/p2p/$node2_id" +} + +function initPolybftConsensus() { + echo "Running with polybft consensus" + genesis_params="--consensus polybft --validator-set-size=4 --bridge-json-rpc http://127.0.0.1:8545" + ./polygon-edge polybft-secrets --insecure --data-dir test-chain- --num 4 + ./polygon-edge manifest +} + +function createGenesis() { + ./polygon-edge genesis $genesis_params \ + --block-gas-limit 10000000 \ + --premine 0x85da99c8a7c2c95964c8efd687e95e632fc533d6:1000000000000000000000 \ + --epoch-size 10 +} + +function startServerFromBinary() { + ./polygon-edge server --data-dir ./test-chain-1 --chain genesis.json --grpc-address :10000 --libp2p :30301 --jsonrpc :10002 --seal --log-level DEBUG & + ./polygon-edge server --data-dir ./test-chain-2 --chain genesis.json --grpc-address :20000 --libp2p :30302 --jsonrpc :20002 --seal --log-level DEBUG & + ./polygon-edge server --data-dir ./test-chain-3 --chain genesis.json --grpc-address :30000 --libp2p :30303 --jsonrpc :30002 --seal --log-level DEBUG & + ./polygon-edge server --data-dir ./test-chain-4 --chain genesis.json --grpc-address :40000 --libp2p :30304 --jsonrpc :40002 --seal --log-level DEBUG & + wait +} + +function startServerFromDockerCompose() { + case "$1" in + "ibft") + docker-compose -f ./docker/local/docker-compose.yml up -d --build + ;; + "polybft") + cd core-contracts && npm install && npm run compile && cd - + go run ./consensus/polybft/contractsapi/artifacts-gen/main.go + EDGE_CONSENSUS=polybft docker-compose -f ./docker/local/docker-compose.yml up -d --build + ;; + esac +} + +function destroyDockerEnvironment() { + docker-compose -f ./docker/local/docker-compose.yml down -v +} + +function stopDockerEnvironment() { + docker-compose -f ./docker/local/docker-compose.yml stop +} + +set -e + +# Reset test-dirs +rm -rf test-chain-* +rm -f genesis.json +rm -f manifest.json + +# Build binary +go build -o polygon-edge . + +# If --docker flag is set run docker environment otherwise run from binary +case "$2" in + "--docker") + # cluster {consensus} --docker destroy + if [ "$3" == "destroy" ]; then + destroyDockerEnvironment + echo "Docker $1 environment destroyed!" + exit 0 + # cluster {consensus} --docker stop + elif [ "$3" == "stop" ]; then + stopDockerEnvironment + echo "Docker $1 environment stoped!" + exit 0; + fi + + # cluster {consensus} --docker + echo "Running $1 docker environment..." + startServerFromDockerCompose $1 + echo "Docker $1 environment deployed." + exit 0 + ;; + # cluster {consensus} + *) + echo "Running $1 environment from local binary..." + # Initialize ibft or polybft consensus + if [ "$1" == "ibft" ]; then + # Initialize ibft consensus + initIbftConsensus + # Create genesis file and start the server from binary + createGenesis + startServerFromBinary + exit 0; + elif [ "$1" == "polybft" ]; then + # Initialize polybft consensus + initPolybftConsensus + # Create genesis file and start the server from binary + createGenesis + startServerFromBinary + exit 0; + else + echo "Unsupported consensus mode. Supported modes are: ibft and polybft " + exit 1; + fi + ;; +esac diff --git a/secrets/config.go b/secrets/config.go index badc96dcc2..f28faa2ec3 100644 --- a/secrets/config.go +++ b/secrets/config.go @@ -3,6 +3,8 @@ package secrets import ( "encoding/json" "os" + + "github.com/0xPolygon/polygon-edge/helper/common" ) // SecretsManagerConfig is the configuration that gets @@ -20,7 +22,7 @@ type SecretsManagerConfig struct { func (c *SecretsManagerConfig) WriteConfig(path string) error { jsonBytes, _ := json.MarshalIndent(c, "", " ") - return os.WriteFile(path, jsonBytes, os.ModePerm) + return common.SaveFileSafe(path, jsonBytes, 0660) } // ReadConfig reads the SecretsManagerConfig from the specified path diff --git a/secrets/helper/helper.go b/secrets/helper/helper.go index 10ef02cfc3..aa9ec81736 100644 --- a/secrets/helper/helper.go +++ b/secrets/helper/helper.go @@ -158,7 +158,7 @@ func LoadValidatorAddress(secretsManager secrets.SecretsManager) (types.Address, return crypto.PubKeyToAddress(&privateKey.PublicKey), nil } -// LoadValidatorAddress loads BLS key by SecretsManager and returns BLS Public Key +// LoadBLSPublicKey loads BLS key by SecretsManager and returns BLS Public Key func LoadBLSPublicKey(secretsManager secrets.SecretsManager) (string, error) { if !secretsManager.HasSecret(secrets.ValidatorBLSKey) { return "", nil @@ -206,7 +206,7 @@ func LoadNodeID(secretsManager secrets.SecretsManager) (string, error) { return nodeID.String(), nil } -// GetCloudSecretsManager returns the cloud secrets manager from the provided config +// InitCloudSecretsManager returns the cloud secrets manager from the provided config func InitCloudSecretsManager(secretsConfig *secrets.SecretsManagerConfig) (secrets.SecretsManager, error) { var secretsManager secrets.SecretsManager diff --git a/secrets/local/local.go b/secrets/local/local.go index cbae36402f..2a08d1a36d 100644 --- a/secrets/local/local.go +++ b/secrets/local/local.go @@ -66,7 +66,7 @@ func (l *LocalSecretsManager) Setup() error { subDirectories := []string{secrets.ConsensusFolderLocal, secrets.NetworkFolderLocal} // Set up the local directories - if err := common.SetupDataDir(l.path, subDirectories); err != nil { + if err := common.SetupDataDir(l.path, subDirectories, 0770); err != nil { return err } @@ -140,7 +140,7 @@ func (l *LocalSecretsManager) SetSecret(name string, value []byte) error { ) } // Write the secret to disk - if err := os.WriteFile(secretPath, value, os.ModePerm); err != nil { + if err := common.SaveFileSafe(secretPath, value, 0440); err != nil { return fmt.Errorf( "unable to write secret to disk (%s), %w", secretPath, diff --git a/secrets/local/local_test.go b/secrets/local/local_test.go index adb2137ba0..21d57def50 100644 --- a/secrets/local/local_test.go +++ b/secrets/local/local_test.go @@ -77,7 +77,7 @@ func getLocalSecretsManager(t *testing.T) secrets.SecretsManager { t.Fatalf("Unable to instantiate local secrets manager directories, %v", tempErr) } - setupErr := common.SetupDataDir(workingDirectory, []string{secrets.ConsensusFolderLocal, secrets.NetworkFolderLocal}) + setupErr := common.SetupDataDir(workingDirectory, []string{secrets.ConsensusFolderLocal, secrets.NetworkFolderLocal}, 0770) if setupErr != nil { t.Fatalf("Unable to instantiate local secrets manager directories, %v", setupErr) } diff --git a/server/builtin.go b/server/builtin.go index 8dd5f9d5ec..8aa7a49c80 100644 --- a/server/builtin.go +++ b/server/builtin.go @@ -1,29 +1,36 @@ package server import ( + "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/consensus" consensusDev "github.com/0xPolygon/polygon-edge/consensus/dev" consensusDummy "github.com/0xPolygon/polygon-edge/consensus/dummy" consensusIBFT "github.com/0xPolygon/polygon-edge/consensus/ibft" + consensusPolyBFT "github.com/0xPolygon/polygon-edge/consensus/polybft" "github.com/0xPolygon/polygon-edge/secrets" "github.com/0xPolygon/polygon-edge/secrets/awsssm" "github.com/0xPolygon/polygon-edge/secrets/gcpssm" "github.com/0xPolygon/polygon-edge/secrets/hashicorpvault" "github.com/0xPolygon/polygon-edge/secrets/local" + "github.com/0xPolygon/polygon-edge/state" ) +type GenesisFactoryHook func(config *chain.Chain, engineName string) func(*state.Transition) error + type ConsensusType string const ( - DevConsensus ConsensusType = "dev" - IBFTConsensus ConsensusType = "ibft" - DummyConsensus ConsensusType = "dummy" + DevConsensus ConsensusType = "dev" + IBFTConsensus ConsensusType = "ibft" + PolyBFTConsensus ConsensusType = "polybft" + DummyConsensus ConsensusType = "dummy" ) var consensusBackends = map[ConsensusType]consensus.Factory{ - DevConsensus: consensusDev.Factory, - IBFTConsensus: consensusIBFT.Factory, - DummyConsensus: consensusDummy.Factory, + DevConsensus: consensusDev.Factory, + IBFTConsensus: consensusIBFT.Factory, + PolyBFTConsensus: consensusPolyBFT.Factory, + DummyConsensus: consensusDummy.Factory, } // secretsManagerBackends defines the SecretManager factories for different @@ -35,6 +42,10 @@ var secretsManagerBackends = map[secrets.SecretsManagerType]secrets.SecretsManag secrets.GCPSSM: gcpssm.SecretsManagerFactory, } +var genesisCreationFactory = map[ConsensusType]GenesisFactoryHook{ + PolyBFTConsensus: consensusPolyBFT.GenesisPostHookFactory, +} + func ConsensusSupported(value string) bool { _, ok := consensusBackends[ConsensusType(value)] diff --git a/server/config.go b/server/config.go index 8c20ed94d1..70a50ce4e6 100644 --- a/server/config.go +++ b/server/config.go @@ -41,6 +41,8 @@ type Config struct { JSONLogFormat bool LogFilePath string + + Relayer bool } // Telemetry holds the config details for metric services diff --git a/server/proto/system.pb.go b/server/proto/system.pb.go index ace5018810..83fb12ca8a 100644 --- a/server/proto/system.pb.go +++ b/server/proto/system.pb.go @@ -1,8 +1,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 -// protoc v3.19.4 -// source: system.proto +// protoc-gen-go v1.28.1 +// protoc v3.21.7 +// source: server/proto/system.proto package proto @@ -33,7 +33,7 @@ type BlockchainEvent struct { func (x *BlockchainEvent) Reset() { *x = BlockchainEvent{} if protoimpl.UnsafeEnabled { - mi := &file_system_proto_msgTypes[0] + mi := &file_server_proto_system_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -46,7 +46,7 @@ func (x *BlockchainEvent) String() string { func (*BlockchainEvent) ProtoMessage() {} func (x *BlockchainEvent) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[0] + mi := &file_server_proto_system_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -59,7 +59,7 @@ func (x *BlockchainEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use BlockchainEvent.ProtoReflect.Descriptor instead. func (*BlockchainEvent) Descriptor() ([]byte, []int) { - return file_system_proto_rawDescGZIP(), []int{0} + return file_server_proto_system_proto_rawDescGZIP(), []int{0} } func (x *BlockchainEvent) GetAdded() []*BlockchainEvent_Header { @@ -90,7 +90,7 @@ type ServerStatus struct { func (x *ServerStatus) Reset() { *x = ServerStatus{} if protoimpl.UnsafeEnabled { - mi := &file_system_proto_msgTypes[1] + mi := &file_server_proto_system_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -103,7 +103,7 @@ func (x *ServerStatus) String() string { func (*ServerStatus) ProtoMessage() {} func (x *ServerStatus) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[1] + mi := &file_server_proto_system_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -116,7 +116,7 @@ func (x *ServerStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ServerStatus.ProtoReflect.Descriptor instead. func (*ServerStatus) Descriptor() ([]byte, []int) { - return file_system_proto_rawDescGZIP(), []int{1} + return file_server_proto_system_proto_rawDescGZIP(), []int{1} } func (x *ServerStatus) GetNetwork() int64 { @@ -160,7 +160,7 @@ type Peer struct { func (x *Peer) Reset() { *x = Peer{} if protoimpl.UnsafeEnabled { - mi := &file_system_proto_msgTypes[2] + mi := &file_server_proto_system_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -173,7 +173,7 @@ func (x *Peer) String() string { func (*Peer) ProtoMessage() {} func (x *Peer) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[2] + mi := &file_server_proto_system_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -186,7 +186,7 @@ func (x *Peer) ProtoReflect() protoreflect.Message { // Deprecated: Use Peer.ProtoReflect.Descriptor instead. func (*Peer) Descriptor() ([]byte, []int) { - return file_system_proto_rawDescGZIP(), []int{2} + return file_server_proto_system_proto_rawDescGZIP(), []int{2} } func (x *Peer) GetId() string { @@ -221,7 +221,7 @@ type PeersAddRequest struct { func (x *PeersAddRequest) Reset() { *x = PeersAddRequest{} if protoimpl.UnsafeEnabled { - mi := &file_system_proto_msgTypes[3] + mi := &file_server_proto_system_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -234,7 +234,7 @@ func (x *PeersAddRequest) String() string { func (*PeersAddRequest) ProtoMessage() {} func (x *PeersAddRequest) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[3] + mi := &file_server_proto_system_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -247,7 +247,7 @@ func (x *PeersAddRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeersAddRequest.ProtoReflect.Descriptor instead. func (*PeersAddRequest) Descriptor() ([]byte, []int) { - return file_system_proto_rawDescGZIP(), []int{3} + return file_server_proto_system_proto_rawDescGZIP(), []int{3} } func (x *PeersAddRequest) GetId() string { @@ -268,7 +268,7 @@ type PeersAddResponse struct { func (x *PeersAddResponse) Reset() { *x = PeersAddResponse{} if protoimpl.UnsafeEnabled { - mi := &file_system_proto_msgTypes[4] + mi := &file_server_proto_system_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -281,7 +281,7 @@ func (x *PeersAddResponse) String() string { func (*PeersAddResponse) ProtoMessage() {} func (x *PeersAddResponse) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[4] + mi := &file_server_proto_system_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -294,7 +294,7 @@ func (x *PeersAddResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeersAddResponse.ProtoReflect.Descriptor instead. func (*PeersAddResponse) Descriptor() ([]byte, []int) { - return file_system_proto_rawDescGZIP(), []int{4} + return file_server_proto_system_proto_rawDescGZIP(), []int{4} } func (x *PeersAddResponse) GetMessage() string { @@ -315,7 +315,7 @@ type PeersStatusRequest struct { func (x *PeersStatusRequest) Reset() { *x = PeersStatusRequest{} if protoimpl.UnsafeEnabled { - mi := &file_system_proto_msgTypes[5] + mi := &file_server_proto_system_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -328,7 +328,7 @@ func (x *PeersStatusRequest) String() string { func (*PeersStatusRequest) ProtoMessage() {} func (x *PeersStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[5] + mi := &file_server_proto_system_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -341,7 +341,7 @@ func (x *PeersStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeersStatusRequest.ProtoReflect.Descriptor instead. func (*PeersStatusRequest) Descriptor() ([]byte, []int) { - return file_system_proto_rawDescGZIP(), []int{5} + return file_server_proto_system_proto_rawDescGZIP(), []int{5} } func (x *PeersStatusRequest) GetId() string { @@ -362,7 +362,7 @@ type PeersListResponse struct { func (x *PeersListResponse) Reset() { *x = PeersListResponse{} if protoimpl.UnsafeEnabled { - mi := &file_system_proto_msgTypes[6] + mi := &file_server_proto_system_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -375,7 +375,7 @@ func (x *PeersListResponse) String() string { func (*PeersListResponse) ProtoMessage() {} func (x *PeersListResponse) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[6] + mi := &file_server_proto_system_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -388,7 +388,7 @@ func (x *PeersListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeersListResponse.ProtoReflect.Descriptor instead. func (*PeersListResponse) Descriptor() ([]byte, []int) { - return file_system_proto_rawDescGZIP(), []int{6} + return file_server_proto_system_proto_rawDescGZIP(), []int{6} } func (x *PeersListResponse) GetPeers() []*Peer { @@ -409,7 +409,7 @@ type BlockByNumberRequest struct { func (x *BlockByNumberRequest) Reset() { *x = BlockByNumberRequest{} if protoimpl.UnsafeEnabled { - mi := &file_system_proto_msgTypes[7] + mi := &file_server_proto_system_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -422,7 +422,7 @@ func (x *BlockByNumberRequest) String() string { func (*BlockByNumberRequest) ProtoMessage() {} func (x *BlockByNumberRequest) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[7] + mi := &file_server_proto_system_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -435,7 +435,7 @@ func (x *BlockByNumberRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BlockByNumberRequest.ProtoReflect.Descriptor instead. func (*BlockByNumberRequest) Descriptor() ([]byte, []int) { - return file_system_proto_rawDescGZIP(), []int{7} + return file_server_proto_system_proto_rawDescGZIP(), []int{7} } func (x *BlockByNumberRequest) GetNumber() uint64 { @@ -456,7 +456,7 @@ type BlockResponse struct { func (x *BlockResponse) Reset() { *x = BlockResponse{} if protoimpl.UnsafeEnabled { - mi := &file_system_proto_msgTypes[8] + mi := &file_server_proto_system_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -469,7 +469,7 @@ func (x *BlockResponse) String() string { func (*BlockResponse) ProtoMessage() {} func (x *BlockResponse) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[8] + mi := &file_server_proto_system_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -482,7 +482,7 @@ func (x *BlockResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BlockResponse.ProtoReflect.Descriptor instead. func (*BlockResponse) Descriptor() ([]byte, []int) { - return file_system_proto_rawDescGZIP(), []int{8} + return file_server_proto_system_proto_rawDescGZIP(), []int{8} } func (x *BlockResponse) GetData() []byte { @@ -504,7 +504,7 @@ type ExportRequest struct { func (x *ExportRequest) Reset() { *x = ExportRequest{} if protoimpl.UnsafeEnabled { - mi := &file_system_proto_msgTypes[9] + mi := &file_server_proto_system_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -517,7 +517,7 @@ func (x *ExportRequest) String() string { func (*ExportRequest) ProtoMessage() {} func (x *ExportRequest) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[9] + mi := &file_server_proto_system_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -530,7 +530,7 @@ func (x *ExportRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExportRequest.ProtoReflect.Descriptor instead. func (*ExportRequest) Descriptor() ([]byte, []int) { - return file_system_proto_rawDescGZIP(), []int{9} + return file_server_proto_system_proto_rawDescGZIP(), []int{9} } func (x *ExportRequest) GetFrom() uint64 { @@ -562,7 +562,7 @@ type ExportEvent struct { func (x *ExportEvent) Reset() { *x = ExportEvent{} if protoimpl.UnsafeEnabled { - mi := &file_system_proto_msgTypes[10] + mi := &file_server_proto_system_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -575,7 +575,7 @@ func (x *ExportEvent) String() string { func (*ExportEvent) ProtoMessage() {} func (x *ExportEvent) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[10] + mi := &file_server_proto_system_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -588,7 +588,7 @@ func (x *ExportEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ExportEvent.ProtoReflect.Descriptor instead. func (*ExportEvent) Descriptor() ([]byte, []int) { - return file_system_proto_rawDescGZIP(), []int{10} + return file_server_proto_system_proto_rawDescGZIP(), []int{10} } func (x *ExportEvent) GetFrom() uint64 { @@ -631,7 +631,7 @@ type BlockchainEvent_Header struct { func (x *BlockchainEvent_Header) Reset() { *x = BlockchainEvent_Header{} if protoimpl.UnsafeEnabled { - mi := &file_system_proto_msgTypes[11] + mi := &file_server_proto_system_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -644,7 +644,7 @@ func (x *BlockchainEvent_Header) String() string { func (*BlockchainEvent_Header) ProtoMessage() {} func (x *BlockchainEvent_Header) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[11] + mi := &file_server_proto_system_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -657,7 +657,7 @@ func (x *BlockchainEvent_Header) ProtoReflect() protoreflect.Message { // Deprecated: Use BlockchainEvent_Header.ProtoReflect.Descriptor instead. func (*BlockchainEvent_Header) Descriptor() ([]byte, []int) { - return file_system_proto_rawDescGZIP(), []int{0, 0} + return file_server_proto_system_proto_rawDescGZIP(), []int{0, 0} } func (x *BlockchainEvent_Header) GetNumber() int64 { @@ -686,7 +686,7 @@ type ServerStatus_Block struct { func (x *ServerStatus_Block) Reset() { *x = ServerStatus_Block{} if protoimpl.UnsafeEnabled { - mi := &file_system_proto_msgTypes[12] + mi := &file_server_proto_system_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -699,7 +699,7 @@ func (x *ServerStatus_Block) String() string { func (*ServerStatus_Block) ProtoMessage() {} func (x *ServerStatus_Block) ProtoReflect() protoreflect.Message { - mi := &file_system_proto_msgTypes[12] + mi := &file_server_proto_system_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -712,7 +712,7 @@ func (x *ServerStatus_Block) ProtoReflect() protoreflect.Message { // Deprecated: Use ServerStatus_Block.ProtoReflect.Descriptor instead. func (*ServerStatus_Block) Descriptor() ([]byte, []int) { - return file_system_proto_rawDescGZIP(), []int{1, 0} + return file_server_proto_system_proto_rawDescGZIP(), []int{1, 0} } func (x *ServerStatus_Block) GetNumber() int64 { @@ -729,109 +729,109 @@ func (x *ServerStatus_Block) GetHash() string { return "" } -var File_system_proto protoreflect.FileDescriptor - -var file_system_proto_rawDesc = []byte{ - 0x0a, 0x0c, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, - 0x76, 0x31, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, - 0xaf, 0x01, 0x0a, 0x0f, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, - 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x05, - 0x61, 0x64, 0x64, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x07, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, - 0x65, 0x72, 0x52, 0x07, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x1a, 0x34, 0x0a, 0x06, 0x48, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x12, 0x0a, - 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, - 0x68, 0x22, 0xc3, 0x01, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x18, 0x0a, 0x07, - 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, - 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x12, 0x30, 0x0a, 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, - 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x32, 0x70, 0x41, - 0x64, 0x64, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x32, 0x70, 0x41, 0x64, - 0x64, 0x72, 0x1a, 0x33, 0x0a, 0x05, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x6e, - 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6e, 0x75, 0x6d, - 0x62, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0x4a, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x1c, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x12, 0x14, 0x0a, - 0x05, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x61, 0x64, - 0x64, 0x72, 0x73, 0x22, 0x21, 0x0a, 0x0f, 0x50, 0x65, 0x65, 0x72, 0x73, 0x41, 0x64, 0x64, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2c, 0x0a, 0x10, 0x50, 0x65, 0x65, 0x72, 0x73, 0x41, - 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x22, 0x24, 0x0a, 0x12, 0x50, 0x65, 0x65, 0x72, 0x73, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x33, 0x0a, 0x11, 0x50, 0x65, - 0x65, 0x72, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x1e, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, - 0x2e, 0x0a, 0x14, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x42, 0x79, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, - 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, - 0x23, 0x0a, 0x0d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, - 0x64, 0x61, 0x74, 0x61, 0x22, 0x33, 0x0a, 0x0d, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, +var File_server_proto_system_proto protoreflect.FileDescriptor + +var file_server_proto_system_proto_rawDesc = []byte{ + 0x0a, 0x19, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, + 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x76, 0x31, 0x1a, + 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xaf, 0x01, 0x0a, + 0x0f, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x12, 0x30, 0x0a, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x05, 0x61, 0x64, 0x64, + 0x65, 0x64, 0x12, 0x34, 0x0a, 0x07, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, + 0x07, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x1a, 0x34, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, + 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0xc3, + 0x01, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x18, 0x0a, 0x07, 0x67, 0x65, 0x6e, + 0x65, 0x73, 0x69, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x65, 0x6e, 0x65, + 0x73, 0x69, 0x73, 0x12, 0x30, 0x0a, 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x07, 0x63, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x32, 0x70, 0x41, 0x64, 0x64, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x32, 0x70, 0x41, 0x64, 0x64, 0x72, 0x1a, + 0x33, 0x0a, 0x05, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, + 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, + 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x68, 0x61, 0x73, 0x68, 0x22, 0x4a, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x09, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x09, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x64, + 0x64, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x61, 0x64, 0x64, 0x72, 0x73, + 0x22, 0x21, 0x0a, 0x0f, 0x50, 0x65, 0x65, 0x72, 0x73, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x22, 0x2c, 0x0a, 0x10, 0x50, 0x65, 0x65, 0x72, 0x73, 0x41, 0x64, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x22, 0x24, 0x0a, 0x12, 0x50, 0x65, 0x65, 0x72, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x33, 0x0a, 0x11, 0x50, 0x65, 0x65, 0x72, 0x73, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x05, + 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x65, 0x65, 0x72, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x2e, 0x0a, 0x14, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x42, 0x79, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x23, 0x0a, 0x0d, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, + 0x61, 0x22, 0x33, 0x0a, 0x0d, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x02, 0x74, 0x6f, 0x22, 0x5d, 0x0a, 0x0b, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x74, 0x6f, 0x22, 0x5d, 0x0a, 0x0b, 0x45, 0x78, 0x70, - 0x6f, 0x72, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, - 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x16, 0x0a, 0x06, - 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6c, 0x61, - 0x74, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x32, 0x8d, 0x03, 0x0a, 0x06, 0x53, 0x79, 0x73, - 0x74, 0x65, 0x6d, 0x12, 0x35, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x35, 0x0a, 0x08, 0x50, 0x65, - 0x65, 0x72, 0x73, 0x41, 0x64, 0x64, 0x12, 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x65, 0x72, - 0x73, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x76, 0x31, - 0x2e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3a, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x65, 0x72, - 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, - 0x0b, 0x50, 0x65, 0x65, 0x72, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x76, - 0x31, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x12, 0x3a, - 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x61, 0x74, + 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6c, 0x61, 0x74, 0x65, 0x73, + 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x32, 0x8d, 0x03, 0x0a, 0x06, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, + 0x12, 0x35, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x35, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x73, + 0x41, 0x64, 0x64, 0x12, 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x41, 0x64, + 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, + 0x65, 0x72, 0x73, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, + 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x1a, 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, - 0x61, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x3c, 0x0a, 0x0d, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x42, 0x79, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x76, 0x31, - 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x42, 0x79, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x06, 0x45, 0x78, 0x70, 0x6f, - 0x72, 0x74, 0x12, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, - 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x42, 0x0f, 0x5a, 0x0d, 0x2f, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x0b, 0x50, 0x65, + 0x65, 0x72, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x2e, 0x76, 0x31, 0x2e, 0x50, + 0x65, 0x65, 0x72, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x12, 0x3a, 0x0a, 0x09, 0x53, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x3c, 0x0a, 0x0d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x42, 0x79, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x42, 0x79, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x06, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x12, + 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x30, 0x01, 0x42, 0x0f, 0x5a, 0x0d, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( - file_system_proto_rawDescOnce sync.Once - file_system_proto_rawDescData = file_system_proto_rawDesc + file_server_proto_system_proto_rawDescOnce sync.Once + file_server_proto_system_proto_rawDescData = file_server_proto_system_proto_rawDesc ) -func file_system_proto_rawDescGZIP() []byte { - file_system_proto_rawDescOnce.Do(func() { - file_system_proto_rawDescData = protoimpl.X.CompressGZIP(file_system_proto_rawDescData) +func file_server_proto_system_proto_rawDescGZIP() []byte { + file_server_proto_system_proto_rawDescOnce.Do(func() { + file_server_proto_system_proto_rawDescData = protoimpl.X.CompressGZIP(file_server_proto_system_proto_rawDescData) }) - return file_system_proto_rawDescData + return file_server_proto_system_proto_rawDescData } -var file_system_proto_msgTypes = make([]protoimpl.MessageInfo, 13) -var file_system_proto_goTypes = []interface{}{ +var file_server_proto_system_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_server_proto_system_proto_goTypes = []interface{}{ (*BlockchainEvent)(nil), // 0: v1.BlockchainEvent (*ServerStatus)(nil), // 1: v1.ServerStatus (*Peer)(nil), // 2: v1.Peer @@ -847,7 +847,7 @@ var file_system_proto_goTypes = []interface{}{ (*ServerStatus_Block)(nil), // 12: v1.ServerStatus.Block (*emptypb.Empty)(nil), // 13: google.protobuf.Empty } -var file_system_proto_depIdxs = []int32{ +var file_server_proto_system_proto_depIdxs = []int32{ 11, // 0: v1.BlockchainEvent.added:type_name -> v1.BlockchainEvent.Header 11, // 1: v1.BlockchainEvent.removed:type_name -> v1.BlockchainEvent.Header 12, // 2: v1.ServerStatus.current:type_name -> v1.ServerStatus.Block @@ -873,13 +873,13 @@ var file_system_proto_depIdxs = []int32{ 0, // [0:4] is the sub-list for field type_name } -func init() { file_system_proto_init() } -func file_system_proto_init() { - if File_system_proto != nil { +func init() { file_server_proto_system_proto_init() } +func file_server_proto_system_proto_init() { + if File_server_proto_system_proto != nil { return } if !protoimpl.UnsafeEnabled { - file_system_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_server_proto_system_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BlockchainEvent); i { case 0: return &v.state @@ -891,7 +891,7 @@ func file_system_proto_init() { return nil } } - file_system_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_server_proto_system_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ServerStatus); i { case 0: return &v.state @@ -903,7 +903,7 @@ func file_system_proto_init() { return nil } } - file_system_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_server_proto_system_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Peer); i { case 0: return &v.state @@ -915,7 +915,7 @@ func file_system_proto_init() { return nil } } - file_system_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_server_proto_system_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PeersAddRequest); i { case 0: return &v.state @@ -927,7 +927,7 @@ func file_system_proto_init() { return nil } } - file_system_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_server_proto_system_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PeersAddResponse); i { case 0: return &v.state @@ -939,7 +939,7 @@ func file_system_proto_init() { return nil } } - file_system_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + file_server_proto_system_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PeersStatusRequest); i { case 0: return &v.state @@ -951,7 +951,7 @@ func file_system_proto_init() { return nil } } - file_system_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + file_server_proto_system_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PeersListResponse); i { case 0: return &v.state @@ -963,7 +963,7 @@ func file_system_proto_init() { return nil } } - file_system_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + file_server_proto_system_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BlockByNumberRequest); i { case 0: return &v.state @@ -975,7 +975,7 @@ func file_system_proto_init() { return nil } } - file_system_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + file_server_proto_system_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BlockResponse); i { case 0: return &v.state @@ -987,7 +987,7 @@ func file_system_proto_init() { return nil } } - file_system_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + file_server_proto_system_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ExportRequest); i { case 0: return &v.state @@ -999,7 +999,7 @@ func file_system_proto_init() { return nil } } - file_system_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + file_server_proto_system_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ExportEvent); i { case 0: return &v.state @@ -1011,7 +1011,7 @@ func file_system_proto_init() { return nil } } - file_system_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + file_server_proto_system_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BlockchainEvent_Header); i { case 0: return &v.state @@ -1023,7 +1023,7 @@ func file_system_proto_init() { return nil } } - file_system_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + file_server_proto_system_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ServerStatus_Block); i { case 0: return &v.state @@ -1040,18 +1040,18 @@ func file_system_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_system_proto_rawDesc, + RawDescriptor: file_server_proto_system_proto_rawDesc, NumEnums: 0, NumMessages: 13, NumExtensions: 0, NumServices: 1, }, - GoTypes: file_system_proto_goTypes, - DependencyIndexes: file_system_proto_depIdxs, - MessageInfos: file_system_proto_msgTypes, + GoTypes: file_server_proto_system_proto_goTypes, + DependencyIndexes: file_server_proto_system_proto_depIdxs, + MessageInfos: file_server_proto_system_proto_msgTypes, }.Build() - File_system_proto = out.File - file_system_proto_rawDesc = nil - file_system_proto_goTypes = nil - file_system_proto_depIdxs = nil + File_server_proto_system_proto = out.File + file_server_proto_system_proto_rawDesc = nil + file_server_proto_system_proto_goTypes = nil + file_server_proto_system_proto_depIdxs = nil } diff --git a/server/proto/system_grpc.pb.go b/server/proto/system_grpc.pb.go index eba359b689..674458d9d4 100644 --- a/server/proto/system_grpc.pb.go +++ b/server/proto/system_grpc.pb.go @@ -1,4 +1,8 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.21.7 +// source: server/proto/system.proto package proto @@ -383,5 +387,5 @@ var System_ServiceDesc = grpc.ServiceDesc{ ServerStreams: true, }, }, - Metadata: "system.proto", + Metadata: "server/proto/system.proto", } diff --git a/server/server.go b/server/server.go index a229415e57..1160a5d470 100644 --- a/server/server.go +++ b/server/server.go @@ -15,6 +15,9 @@ import ( "github.com/0xPolygon/polygon-edge/blockchain" "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/consensus" + "github.com/0xPolygon/polygon-edge/consensus/polybft/statesyncrelayer" + "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" + "github.com/0xPolygon/polygon-edge/contracts" "github.com/0xPolygon/polygon-edge/crypto" "github.com/0xPolygon/polygon-edge/helper/common" configHelper "github.com/0xPolygon/polygon-edge/helper/config" @@ -32,6 +35,7 @@ import ( "github.com/hashicorp/go-hclog" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/umbracle/ethgo" "google.golang.org/grpc" ) @@ -70,6 +74,9 @@ type Server struct { // restore restoreProgression *progress.ProgressionWrapper + + // stateSyncRelayer is handling state syncs execution (Polybft exclusive) + stateSyncRelayer *statesyncrelayer.StateSyncRelayer } var dirPaths = []string{ @@ -136,7 +143,7 @@ func NewServer(config *Config) (*Server, error) { m.logger.Info("Data dir", "path", config.DataDir) // Generate all the paths in the dataDir - if err := common.SetupDataDir(config.DataDir, dirPaths); err != nil { + if err := common.SetupDataDir(config.DataDir, dirPaths, 0770); err != nil { return nil, fmt.Errorf("failed to create data directories: %w", err) } @@ -186,6 +193,12 @@ func NewServer(config *Config) (*Server, error) { m.executor = state.NewExecutor(config.Chain.Params, st, logger) + // custom write genesis hook per consensus engine + engineName := m.config.Chain.Params.GetEngine() + if factory, exists := genesisCreationFactory[ConsensusType(engineName)]; exists { + m.executor.GenesisPostHook = factory(m.config.Chain, engineName) + } + // compute the genesis root state genesisRoot := m.executor.WriteGenesis(config.Chain.Genesis.Alloc) config.Chain.Genesis.StateRoot = genesisRoot @@ -277,6 +290,13 @@ func NewServer(config *Config) (*Server, error) { return nil, err } + // start relayer + if config.Relayer { + if err := m.setupRelayer(); err != nil { + return nil, err + } + } + m.txpool.Start() return m, nil @@ -430,6 +450,29 @@ func (s *Server) setupConsensus() error { return nil } +// setupRelayer sets up the relayer +func (s *Server) setupRelayer() error { + account, err := wallet.NewAccountFromSecret(s.secretsManager) + if err != nil { + return fmt.Errorf("failed to create account from secret: %w", err) + } + + relayer := statesyncrelayer.NewRelayer( + s.config.DataDir, + s.config.JSONRPC.JSONRPCAddr.String(), + ethgo.Address(contracts.StateReceiverContract), + s.logger.Named("relayer"), + wallet.NewEcdsaSigner(wallet.NewKey(account)), + ) + + // start relayer + if err := relayer.Start(); err != nil { + return fmt.Errorf("failed to start relayer: %w", err) + } + + return nil +} + type jsonRPCHub struct { state state.State restoreProgression *progress.ProgressionWrapper @@ -439,6 +482,7 @@ type jsonRPCHub struct { *state.Executor *network.Server consensus.Consensus + consensus.BridgeDataProvider } func (j *jsonRPCHub) GetPeers() int { @@ -659,6 +703,7 @@ func (s *Server) setupJSONRPC() error { Executor: s.executor, Consensus: s.consensus, Server: s.network, + BridgeDataProvider: s.consensus.GetBridgeProvider(), } conf := &jsonrpc.Config{ @@ -740,10 +785,15 @@ func (s *Server) Close() { } } - // close the txpool's main loop + // Stop state sync relayer + if s.stateSyncRelayer != nil { + s.stateSyncRelayer.Stop() + } + + // Close the txpool's main loop s.txpool.Close() - // close DataDog profiler + // Close DataDog profiler s.closeDataDogProfiler() } diff --git a/setup-ci.sh b/setup-ci.sh new file mode 100755 index 0000000000..c985a55c7e --- /dev/null +++ b/setup-ci.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +# set -o errexit + +install_dependencies() { + # Solidity compiler + VERSION="0.5.5" + DOWNLOAD=https://github.com/ethereum/solidity/releases/download/v${VERSION}/solc-static-linux + + curl -L $DOWNLOAD > /tmp/solc + chmod +x /tmp/solc + mv /tmp/solc /usr/local/bin/solc +} + +install_dependencies diff --git a/state/executor.go b/state/executor.go index a34e259546..7c2b18916b 100644 --- a/state/executor.go +++ b/state/executor.go @@ -7,6 +7,7 @@ import ( "math/big" "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/contracts" "github.com/0xPolygon/polygon-edge/crypto" "github.com/0xPolygon/polygon-edge/state/runtime" "github.com/0xPolygon/polygon-edge/state/runtime/evm" @@ -17,7 +18,7 @@ import ( ) const ( - spuriousDragonMaxCodeSize = 24576 + SpuriousDragonMaxCodeSize = 24576 TxGas uint64 = 21000 // Per transaction not creating a contract TxGasContractCreation uint64 = 53000 // Per transaction that creates a contract @@ -37,7 +38,8 @@ type Executor struct { state State GetHash GetHashByNumberHelper - PostHook func(txn *Transition) + PostHook func(txn *Transition) + GenesisPostHook func(*Transition) error } // NewExecutor creates a new executor @@ -52,6 +54,21 @@ func NewExecutor(config *chain.Params, s State, logger hclog.Logger) *Executor { func (e *Executor) WriteGenesis(alloc map[types.Address]*chain.GenesisAccount) types.Hash { snap := e.state.NewSnapshot() txn := NewTxn(snap) + config := e.config.Forks.At(0) + + env := runtime.TxContext{ + ChainID: int64(e.config.ChainID), + } + + transition := &Transition{ + logger: e.logger, + ctx: env, + state: txn, + auxState: e.state, + gasPool: uint64(env.GasLimit), + config: config, + precompiles: precompiled.NewPrecompiled(), + } for addr, account := range alloc { if account.Balance != nil { @@ -71,6 +88,12 @@ func (e *Executor) WriteGenesis(alloc map[types.Address]*chain.GenesisAccount) t } } + if e.GenesisPostHook != nil { + if err := e.GenesisPostHook(transition); err != nil { + panic(fmt.Errorf("Error writing genesis block: %w", err)) + } + } + objs := txn.Commit(false) _, root := snap.Commit(objs) @@ -217,7 +240,7 @@ var emptyFrom = types.Address{} func (t *Transition) WriteFailedReceipt(txn *types.Transaction) error { signer := crypto.NewSigner(t.config, uint64(t.ctx.ChainID)) - if txn.From == emptyFrom { + if txn.From == emptyFrom && txn.Type == types.LegacyTx { // Decrypt the from address from, err := signer.Sender(txn) if err != nil { @@ -229,6 +252,7 @@ func (t *Transition) WriteFailedReceipt(txn *types.Transaction) error { receipt := &types.Receipt{ CumulativeGasUsed: t.totalGas, + TransactionType: txn.Type, TxHash: txn.Hash, Logs: t.state.Logs(), } @@ -246,11 +270,12 @@ func (t *Transition) WriteFailedReceipt(txn *types.Transaction) error { // Write writes another transaction to the executor func (t *Transition) Write(txn *types.Transaction) error { - signer := crypto.NewSigner(t.config, uint64(t.ctx.ChainID)) - var err error - if txn.From == emptyFrom { + + if txn.From == emptyFrom && txn.Type == types.LegacyTx { // Decrypt the from address + signer := crypto.NewSigner(t.config, uint64(t.ctx.ChainID)) + txn.From, err = signer.Sender(txn) if err != nil { return NewTransitionApplicationError(err, false) @@ -273,6 +298,7 @@ func (t *Transition) Write(txn *types.Transaction) error { receipt := &types.Receipt{ CumulativeGasUsed: t.totalGas, + TransactionType: txn.Type, TxHash: txn.Hash, GasUsed: result.GasUsed, } @@ -412,28 +438,17 @@ func NewGasLimitReachedTransitionApplicationError(err error) *GasLimitReachedTra } func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, error) { - // First check this message satisfies all consensus rules before - // applying the message. The rules include these clauses - // - // 1. the nonce of the message caller is correct - // 2. caller has enough balance to cover transaction fee(gaslimit * gasprice) - // 3. the amount of gas required is available in the block - // 4. there is no overflow when calculating intrinsic gas - // 5. the purchased gas is enough to cover intrinsic usage - // 6. caller has enough balance to cover asset transfer for **topmost** call - txn := t.state - - // 1. the nonce of the message caller is correct - if err := t.nonceCheck(msg); err != nil { - return nil, NewTransitionApplicationError(err, true) - } - - // 2. caller has enough balance to cover transaction fee(gaslimit * gasprice) - if err := t.subGasLimitPrice(msg); err != nil { - return nil, NewTransitionApplicationError(err, true) + if msg.Type == types.StateTx { + if err := checkAndProcessStateTx(msg, t); err != nil { + return nil, err + } + } else { + if err := checkAndProcessLegacyTx(msg, t); err != nil { + return nil, err + } } - // 3. the amount of gas required is available in the block + // the amount of gas required is available in the block if err := t.subGasPool(msg.Gas); err != nil { return nil, NewGasLimitReachedTransitionApplicationError(err) } @@ -448,22 +463,17 @@ func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, er return nil, NewTransitionApplicationError(err, false) } - // 5. the purchased gas is enough to cover intrinsic usage + // the purchased gas is enough to cover intrinsic usage gasLeft := msg.Gas - intrinsicGasCost - // Because we are working with unsigned integers for gas, the `>` operator is used instead of the more intuitive `<` + // because we are working with unsigned integers for gas, the `>` operator is used instead of the more intuitive `<` if gasLeft > msg.Gas { return nil, NewTransitionApplicationError(ErrNotEnoughIntrinsicGas, false) } - // 6. caller has enough balance to cover asset transfer for **topmost** call - if balance := txn.GetBalance(msg.From); balance.Cmp(msg.Value) < 0 { - return nil, NewTransitionApplicationError(ErrNotEnoughFunds, true) - } - gasPrice := new(big.Int).Set(msg.GasPrice) value := new(big.Int).Set(msg.Value) - // Set the specific transaction fields in the context + // set the specific transaction fields in the context t.ctx.GasPrice = types.BytesToHash(gasPrice.Bytes()) t.ctx.Origin = msg.From @@ -471,11 +481,11 @@ func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, er if msg.IsContractCreation() { result = t.Create2(msg.From, msg.Input, value, gasLeft) } else { - txn.IncrNonce(msg.From) + t.state.IncrNonce(msg.From) result = t.Call2(msg.From, *msg.To, msg.Input, value, gasLeft) } - refund := txn.GetRefund() + refund := t.state.GetRefund() result.UpdateGasUsed(msg.Gas, refund) if t.ctx.Tracer != nil { @@ -483,12 +493,12 @@ func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, er } // refund the sender - remaining := new(big.Int).Mul(new(big.Int).SetUint64(result.GasLeft), gasPrice) - txn.AddBalance(msg.From, remaining) + remaining := new(big.Int).Mul(new(big.Int).SetUint64(result.GasLeft), msg.GasPrice) + t.state.AddBalance(msg.From, remaining) // pay the coinbase - coinbaseFee := new(big.Int).Mul(new(big.Int).SetUint64(result.GasUsed), gasPrice) - txn.AddBalance(t.ctx.Coinbase, coinbaseFee) + coinbaseFee := new(big.Int).Mul(new(big.Int).SetUint64(result.GasUsed), msg.GasPrice) + t.state.AddBalance(t.ctx.Coinbase, coinbaseFee) // return gas to the pool t.addGasPool(result.GasLeft) @@ -535,7 +545,7 @@ func (t *Transition) run(contract *runtime.Contract, host runtime.Host) *runtime } } -func (t *Transition) transfer(from, to types.Address, amount *big.Int) error { +func (t *Transition) Transfer(from, to types.Address, amount *big.Int) error { if amount == nil { return nil } @@ -570,7 +580,7 @@ func (t *Transition) applyCall( if callType == runtime.Call { // Transfers only allowed on calls - if err := t.transfer(c.Caller, c.Address, c.Value); err != nil { + if err := t.Transfer(c.Caller, c.Address, c.Value); err != nil { return &runtime.ExecutionResult{ GasLeft: c.Gas, Err: err, @@ -640,7 +650,7 @@ func (t *Transition) applyCreate(c *runtime.Contract, host runtime.Host) *runtim } // Transfer the value - if err := t.transfer(c.Caller, c.Address, c.Value); err != nil { + if err := t.Transfer(c.Caller, c.Address, c.Value); err != nil { return &runtime.ExecutionResult{ GasLeft: gasLimit, Err: err, @@ -663,7 +673,7 @@ func (t *Transition) applyCreate(c *runtime.Contract, host runtime.Host) *runtim return result } - if t.config.EIP158 && len(result.ReturnValue) > spuriousDragonMaxCodeSize { + if t.config.EIP158 && len(result.ReturnValue) > SpuriousDragonMaxCodeSize { // Contract size exceeds 'SpuriousDragon' size limit t.state.RevertToSnapshot(snapshot) @@ -690,6 +700,7 @@ func (t *Transition) applyCreate(c *runtime.Contract, host runtime.Host) *runtim } result.GasLeft -= gasCost + result.Address = c.Address t.state.SetCode(c.Address, result.ReturnValue) return result @@ -853,6 +864,56 @@ func TransactionGasCost(msg *types.Transaction, isHomestead, isIstanbul bool) (u return cost, nil } +// checkAndProcessLegacyTx - first check if this message satisfies all consensus rules before +// applying the message. The rules include these clauses: +// 1. the nonce of the message caller is correct +// 2. caller has enough balance to cover transaction fee(gaslimit * gasprice) +func checkAndProcessLegacyTx(msg *types.Transaction, t *Transition) error { + // 1. the nonce of the message caller is correct + if err := t.nonceCheck(msg); err != nil { + return NewTransitionApplicationError(err, true) + } + + // 2. caller has enough balance to cover transaction fee(gaslimit * gasprice) + if err := t.subGasLimitPrice(msg); err != nil { + return NewTransitionApplicationError(err, true) + } + + return nil +} + +func checkAndProcessStateTx(msg *types.Transaction, t *Transition) error { + if msg.GasPrice.Cmp(big.NewInt(0)) != 0 { + return NewTransitionApplicationError( + errors.New("gasPrice of state transaction must be zero"), + true, + ) + } + + if msg.Gas != types.StateTransactionGasLimit { + return NewTransitionApplicationError( + fmt.Errorf("gas of state transaction must be %d", types.StateTransactionGasLimit), + true, + ) + } + + if msg.From != contracts.SystemCaller { + return NewTransitionApplicationError( + fmt.Errorf("state transaction sender must be %v, but got %v", contracts.SystemCaller, msg.From), + true, + ) + } + + if msg.To == nil || *msg.To == types.ZeroAddress { + return NewTransitionApplicationError( + errors.New("to of state transaction must be specified"), + true, + ) + } + + return nil +} + // captureCallStart calls CallStart in Tracer if context has the tracer func (t *Transition) captureCallStart(c *runtime.Contract, callType runtime.CallType) { if t.ctx.Tracer == nil { diff --git a/state/immutable-trie/snapshot.go b/state/immutable-trie/snapshot.go index 13fd20f8ef..00c298ecee 100644 --- a/state/immutable-trie/snapshot.go +++ b/state/immutable-trie/snapshot.go @@ -1,6 +1,8 @@ package itrie import ( + "bytes" + "github.com/0xPolygon/polygon-edge/crypto" "github.com/0xPolygon/polygon-edge/state" "github.com/0xPolygon/polygon-edge/types" @@ -31,7 +33,7 @@ func (s *Snapshot) GetStorage(addr types.Address, root types.Hash, rawkey types. key := crypto.Keccak256(rawkey.Bytes()) - val, ok := trie.Get(key) + val, ok := trie.Get(key, s.state.storage) if !ok { return types.Hash{} } @@ -54,7 +56,7 @@ func (s *Snapshot) GetStorage(addr types.Address, root types.Hash, rawkey types. func (s *Snapshot) GetAccount(addr types.Address) (*state.Account, error) { key := crypto.Keccak256(addr.Bytes()) - data, ok := s.trie.Get(key) + data, ok := s.trie.Get(key, s.state.storage) if !ok { return nil, nil } @@ -72,7 +74,76 @@ func (s *Snapshot) GetCode(hash types.Hash) ([]byte, bool) { } func (s *Snapshot) Commit(objs []*state.Object) (state.Snapshot, []byte) { - trie, root := s.trie.Commit(objs) + batch := s.state.storage.Batch() + + tt := s.trie.Txn(s.state.storage) + tt.batch = batch + + arena := accountArenaPool.Get() + defer accountArenaPool.Put(arena) + + ar1 := stateArenaPool.Get() + defer stateArenaPool.Put(ar1) + + for _, obj := range objs { + if obj.Deleted { + tt.Delete(hashit(obj.Address.Bytes())) + } else { + account := state.Account{ + Balance: obj.Balance, + Nonce: obj.Nonce, + CodeHash: obj.CodeHash.Bytes(), + Root: obj.Root, // old root + } + + if len(obj.Storage) != 0 { + trie, err := s.state.newTrieAt(obj.Root) + if err != nil { + panic(err) + } + + localTxn := trie.Txn(s.state.storage) + localTxn.batch = batch + + for _, entry := range obj.Storage { + k := hashit(entry.Key) + if entry.Deleted { + localTxn.Delete(k) + } else { + vv := ar1.NewBytes(bytes.TrimLeft(entry.Val, "\x00")) + localTxn.Insert(k, vv.MarshalTo(nil)) + } + } + + accountStateRoot, _ := localTxn.Hash() + accountStateTrie := localTxn.Commit() + + // Add this to the cache + s.state.AddState(types.BytesToHash(accountStateRoot), accountStateTrie) + + account.Root = types.BytesToHash(accountStateRoot) + } + + if obj.DirtyCode { + s.state.SetCode(obj.CodeHash, obj.Code) + } + + vv := account.MarshalWith(arena) + data := vv.MarshalTo(nil) + + tt.Insert(hashit(obj.Address.Bytes()), data) + arena.Reset() + } + } + + root, _ := tt.Hash() + + nTrie := tt.Commit() + + // Write all the entries to db + batch.Write() + + s.state.AddState(types.BytesToHash(root), nTrie) - return &Snapshot{trie: trie, state: s.state}, root + return &Snapshot{trie: nTrie, state: s.state}, root } diff --git a/state/immutable-trie/state.go b/state/immutable-trie/state.go index d0538e50f2..6614604b2e 100644 --- a/state/immutable-trie/state.go +++ b/state/immutable-trie/state.go @@ -39,11 +39,7 @@ func (s *State) NewSnapshotAt(root types.Hash) (state.Snapshot, error) { } func (s *State) newTrie() *Trie { - t := NewTrie() - t.state = s - t.storage = s.storage - - return t + return NewTrie() } func (s *State) SetCode(hash types.Hash, code []byte) { @@ -54,6 +50,7 @@ func (s *State) GetCode(hash types.Hash) ([]byte, bool) { return s.storage.GetCode(hash) } +// newTrieAt returns trie with root and if necessary locks state on a trie level func (s *State) newTrieAt(root types.Hash) (*Trie, error) { if root == types.EmptyRootHash { // empty state @@ -67,14 +64,7 @@ func (s *State) newTrieAt(root types.Hash) (*Trie, error) { return nil, fmt.Errorf("invalid type assertion on root: %s", root) } - t.state = s - - trie, ok := tt.(*Trie) - if !ok { - return nil, fmt.Errorf("invalid type assertion on root: %s", root) - } - - return trie, nil + return t, nil } n, ok, err := GetNode(root.Bytes(), s.storage) @@ -87,9 +77,7 @@ func (s *State) newTrieAt(root types.Hash) (*Trie, error) { } t := &Trie{ - root: n, - state: s, - storage: s.storage, + root: n, } return t, nil diff --git a/state/immutable-trie/storage.go b/state/immutable-trie/storage.go index 1172d4719d..1212592af0 100644 --- a/state/immutable-trie/storage.go +++ b/state/immutable-trie/storage.go @@ -2,6 +2,7 @@ package itrie import ( "fmt" + "sync" "github.com/0xPolygon/polygon-edge/helper/hex" "github.com/0xPolygon/polygon-edge/types" @@ -95,26 +96,34 @@ func NewLevelDBStorage(path string, logger hclog.Logger) (Storage, error) { } type memStorage struct { + l *sync.Mutex db map[string][]byte code map[string][]byte } type memBatch struct { + l *sync.Mutex db *map[string][]byte } // NewMemoryStorage creates an inmemory trie storage func NewMemoryStorage() Storage { - return &memStorage{db: map[string][]byte{}, code: map[string][]byte{}} + return &memStorage{db: map[string][]byte{}, code: map[string][]byte{}, l: new(sync.Mutex)} } func (m *memStorage) Put(p []byte, v []byte) { + m.l.Lock() + defer m.l.Unlock() + buf := make([]byte, len(v)) copy(buf[:], v[:]) m.db[hex.EncodeToHex(p)] = buf } func (m *memStorage) Get(p []byte) ([]byte, bool) { + m.l.Lock() + defer m.l.Unlock() + v, ok := m.db[hex.EncodeToHex(p)] if !ok { return []byte{}, false @@ -124,17 +133,23 @@ func (m *memStorage) Get(p []byte) ([]byte, bool) { } func (m *memStorage) SetCode(hash types.Hash, code []byte) { + m.l.Lock() + defer m.l.Unlock() + m.code[hash.String()] = code } func (m *memStorage) GetCode(hash types.Hash) ([]byte, bool) { + m.l.Lock() + defer m.l.Unlock() + code, ok := m.code[hash.String()] return code, ok } func (m *memStorage) Batch() Batch { - return &memBatch{db: &m.db} + return &memBatch{db: &m.db, l: new(sync.Mutex)} } func (m *memStorage) Close() error { @@ -142,6 +157,9 @@ func (m *memStorage) Close() error { } func (m *memBatch) Put(p, v []byte) { + m.l.Lock() + defer m.l.Unlock() + buf := make([]byte, len(v)) copy(buf[:], v[:]) (*m.db)[hex.EncodeToHex(p)] = buf diff --git a/state/immutable-trie/trie.go b/state/immutable-trie/trie.go index d802119cd0..55587f059c 100644 --- a/state/immutable-trie/trie.go +++ b/state/immutable-trie/trie.go @@ -4,10 +4,11 @@ import ( "bytes" "fmt" - "github.com/0xPolygon/polygon-edge/state" - "github.com/0xPolygon/polygon-edge/types" "github.com/umbracle/fastrlp" "golang.org/x/crypto/sha3" + + commonHelpers "github.com/0xPolygon/polygon-edge/helper/common" + "github.com/0xPolygon/polygon-edge/types" ) // Node represents a node reference @@ -44,7 +45,7 @@ func (c *common) Hash() ([]byte, bool) { // SetHash implements the node interface func (c *common) SetHash(b []byte) []byte { - c.hash = extendByteSlice(c.hash, len(b)) + c.hash = commonHelpers.ExtendByteSlice(c.hash, len(b)) copy(c.hash, b) return c.hash @@ -90,18 +91,16 @@ func (f *FullNode) getEdge(idx byte) Node { } type Trie struct { - state *State - root Node - epoch uint32 - storage Storage + root Node + epoch uint32 } func NewTrie() *Trie { return &Trie{} } -func (t *Trie) Get(k []byte) ([]byte, bool) { - txn := t.Txn() +func (t *Trie) Get(k []byte, storage Storage) ([]byte, bool) { + txn := t.Txn(storage) res := txn.Lookup(k) return res, res != nil @@ -118,84 +117,6 @@ var accountArenaPool fastrlp.ArenaPool var stateArenaPool fastrlp.ArenaPool // TODO, Remove once we do update in fastrlp -func (t *Trie) Commit(objs []*state.Object) (*Trie, []byte) { - // Create an insertion batch for all the entries - batch := t.storage.Batch() - - tt := t.Txn() - tt.batch = batch - - arena := accountArenaPool.Get() - defer accountArenaPool.Put(arena) - - ar1 := stateArenaPool.Get() - defer stateArenaPool.Put(ar1) - - for _, obj := range objs { - if obj.Deleted { - tt.Delete(hashit(obj.Address.Bytes())) - } else { - account := state.Account{ - Balance: obj.Balance, - Nonce: obj.Nonce, - CodeHash: obj.CodeHash.Bytes(), - Root: obj.Root, // old root - } - - if len(obj.Storage) != 0 { - trie, err := t.state.newTrieAt(obj.Root) - if err != nil { - panic(err) - } - - localTxn := trie.Txn() - localTxn.batch = batch - - for _, entry := range obj.Storage { - k := hashit(entry.Key) - if entry.Deleted { - localTxn.Delete(k) - } else { - vv := ar1.NewBytes(bytes.TrimLeft(entry.Val, "\x00")) - localTxn.Insert(k, vv.MarshalTo(nil)) - } - } - - accountStateRoot, _ := localTxn.Hash() - accountStateTrie := localTxn.Commit() - - // Add this to the cache - t.state.AddState(types.BytesToHash(accountStateRoot), accountStateTrie) - - account.Root = types.BytesToHash(accountStateRoot) - } - - if obj.DirtyCode { - t.state.SetCode(obj.CodeHash, obj.Code) - } - - vv := account.MarshalWith(arena) - data := vv.MarshalTo(nil) - - tt.Insert(hashit(obj.Address.Bytes()), data) - arena.Reset() - } - } - - root, _ := tt.Hash() - - nTrie := tt.Commit() - nTrie.state = t.state - nTrie.storage = t.storage - - // Write all the entries to db - batch.Write() - - t.state.AddState(types.BytesToHash(root), nTrie) - - return nTrie, root -} - // Hash returns the root hash of the trie. It does not write to the // database and can be used even if the trie doesn't have one. func (t *Trie) Hash() types.Hash { @@ -203,39 +124,19 @@ func (t *Trie) Hash() types.Hash { return types.EmptyRootHash } - hash, cached, _ := t.hashRoot() - t.root = cached + hash := t.hashRoot() return types.BytesToHash(hash) } -func (t *Trie) TryUpdate(key, value []byte) error { - k := bytesToHexNibbles(key) - - if len(value) != 0 { - tt := t.Txn() - n := tt.insert(t.root, k, value) - t.root = n - } else { - tt := t.Txn() - n, ok := tt.delete(t.root, k) - if !ok { - return fmt.Errorf("missing node") - } - t.root = n - } - - return nil -} - -func (t *Trie) hashRoot() ([]byte, Node, error) { +func (t *Trie) hashRoot() []byte { hash, _ := t.root.Hash() - return hash, t.root, nil + return hash } -func (t *Trie) Txn() *Txn { - return &Txn{root: t.root, epoch: t.epoch + 1, storage: t.storage} +func (t *Trie) Txn(storage Storage) *Txn { + return &Txn{root: t.root, epoch: t.epoch + 1, storage: storage} } type Putter interface { @@ -250,7 +151,7 @@ type Txn struct { } func (t *Txn) Commit() *Trie { - return &Trie{epoch: t.epoch, root: t.root, storage: t.storage} + return &Trie{epoch: t.epoch, root: t.root} } func (t *Txn) Lookup(key []byte) []byte { @@ -605,12 +506,3 @@ func concat(a, b []byte) []byte { return c } - -func extendByteSlice(b []byte, needLen int) []byte { - b = b[:cap(b)] - if n := needLen - cap(b); n > 0 { - b = append(b, make([]byte, n)...) - } - - return b[:needLen] -} diff --git a/state/runtime/evm/bitmap.go b/state/runtime/evm/bitmap.go index a891876cd5..07e2b76917 100644 --- a/state/runtime/evm/bitmap.go +++ b/state/runtime/evm/bitmap.go @@ -1,5 +1,7 @@ package evm +import "github.com/0xPolygon/polygon-edge/helper/common" + const bitmapSize = uint(8) type bitmap struct { @@ -24,7 +26,7 @@ func (b *bitmap) reset() { func (b *bitmap) setCode(code []byte) { codeSize := uint(len(code)) - b.buf = extendByteSlice(b.buf, int(codeSize/bitmapSize+1)) + b.buf = common.ExtendByteSlice(b.buf, int(codeSize/bitmapSize+1)) for i := uint(0); i < codeSize; { c := code[i] diff --git a/state/runtime/evm/evm_test.go b/state/runtime/evm/evm_test.go index 33652f07d3..e29cfd2b38 100644 --- a/state/runtime/evm/evm_test.go +++ b/state/runtime/evm/evm_test.go @@ -90,6 +90,10 @@ func (m *mockHost) GetNonce(addr types.Address) uint64 { panic("Not implemented in tests") } +func (m *mockHost) Transfer(from types.Address, to types.Address, amount *big.Int) error { + panic("Not implemented in tests") +} + func (m *mockHost) GetTracer() runtime.VMTracer { return m.tracer } diff --git a/state/runtime/evm/state.go b/state/runtime/evm/state.go index c0087d426d..27f64fcce8 100644 --- a/state/runtime/evm/state.go +++ b/state/runtime/evm/state.go @@ -8,6 +8,7 @@ import ( "sync" "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/helper/common" "github.com/0xPolygon/polygon-edge/helper/hex" "github.com/0xPolygon/polygon-edge/state/runtime" "github.com/0xPolygon/polygon-edge/types" @@ -328,21 +329,12 @@ func (c *state) allocateMemory(offset, size *big.Int) bool { } // resize the memory - c.memory = extendByteSlice(c.memory, int(w*32)) + c.memory = common.ExtendByteSlice(c.memory, int(w*32)) } return true } -func extendByteSlice(b []byte, needLen int) []byte { - b = b[:cap(b)] - if n := needLen - cap(b); n > 0 { - b = append(b, make([]byte, n)...) - } - - return b[:needLen] -} - func (c *state) get2(dst []byte, offset, length *big.Int) ([]byte, bool) { if length.Sign() == 0 { return nil, true diff --git a/state/runtime/precompiled/base.go b/state/runtime/precompiled/base.go index 0cf93d77a2..6fc2ab030e 100644 --- a/state/runtime/precompiled/base.go +++ b/state/runtime/precompiled/base.go @@ -9,6 +9,8 @@ import ( "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/crypto" "github.com/0xPolygon/polygon-edge/helper/keccak" + "github.com/0xPolygon/polygon-edge/state/runtime" + "github.com/0xPolygon/polygon-edge/types" ) type ecrecover struct { @@ -19,7 +21,7 @@ func (e *ecrecover) gas(input []byte, config *chain.ForksInTime) uint64 { return 3000 } -func (e *ecrecover) run(input []byte) ([]byte, error) { +func (e *ecrecover) run(input []byte, caller types.Address, _ runtime.Host) ([]byte, error) { input, _ = e.p.get(input, 128) // recover the value v. Expect all zeros except the last byte @@ -55,8 +57,8 @@ func (i *identity) gas(input []byte, config *chain.ForksInTime) uint64 { return baseGasCalc(input, 15, 3) } -func (i *identity) run(in []byte) ([]byte, error) { - return in, nil +func (i *identity) run(input []byte, _ types.Address, _ runtime.Host) ([]byte, error) { + return input, nil } type sha256h struct { @@ -66,7 +68,7 @@ func (s *sha256h) gas(input []byte, config *chain.ForksInTime) uint64 { return baseGasCalc(input, 60, 12) } -func (s *sha256h) run(input []byte) ([]byte, error) { +func (s *sha256h) run(input []byte, _ types.Address, _ runtime.Host) ([]byte, error) { h := sha256.Sum256(input) return h[:], nil @@ -80,7 +82,7 @@ func (r *ripemd160h) gas(input []byte, config *chain.ForksInTime) uint64 { return baseGasCalc(input, 600, 120) } -func (r *ripemd160h) run(input []byte) ([]byte, error) { +func (r *ripemd160h) run(input []byte, _ types.Address, _ runtime.Host) ([]byte, error) { ripemd := ripemd160.New() ripemd.Write(input) res := ripemd.Sum(nil) diff --git a/state/runtime/precompiled/base_test.go b/state/runtime/precompiled/base_test.go index 5bc5a3b93c..c93982e0eb 100644 --- a/state/runtime/precompiled/base_test.go +++ b/state/runtime/precompiled/base_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/0xPolygon/polygon-edge/helper/hex" + "github.com/0xPolygon/polygon-edge/types" ) type precompiledTest struct { @@ -20,7 +21,7 @@ func testPrecompiled(t *testing.T, p contract, cases []precompiledTest) { for _, c := range cases { t.Run(c.Name, func(t *testing.T) { h, _ := hex.DecodeString(c.Input) - found, err := p.run(h) + found, err := p.run(h, types.ZeroAddress, nil) assert.NoError(t, err) assert.Equal(t, c.Expected, hex.EncodeToString(found)) diff --git a/state/runtime/precompiled/blake2f.go b/state/runtime/precompiled/blake2f.go index 6f53227d0d..ab7726c302 100644 --- a/state/runtime/precompiled/blake2f.go +++ b/state/runtime/precompiled/blake2f.go @@ -6,6 +6,8 @@ import ( "math/bits" "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/state/runtime" + "github.com/0xPolygon/polygon-edge/types" ) type blake2f struct { @@ -20,7 +22,7 @@ func (e *blake2f) gas(input []byte, config *chain.ForksInTime) uint64 { return uint64(binary.BigEndian.Uint32(input[0:4])) } -func (e *blake2f) run(input []byte) ([]byte, error) { +func (e *blake2f) run(input []byte, _ types.Address, _ runtime.Host) ([]byte, error) { // validate input if len(input) != 213 { return nil, fmt.Errorf("bad length") diff --git a/state/runtime/precompiled/blake2f_test.go b/state/runtime/precompiled/blake2f_test.go index d89fc81a49..c825260229 100644 --- a/state/runtime/precompiled/blake2f_test.go +++ b/state/runtime/precompiled/blake2f_test.go @@ -3,6 +3,8 @@ package precompiled import ( "bytes" "testing" + + "github.com/0xPolygon/polygon-edge/types" ) func TestBlake2f(t *testing.T) { @@ -11,7 +13,7 @@ func TestBlake2f(t *testing.T) { ReadTestCase(t, "blake2f.json", func(t *testing.T, c *TestCase) { t.Helper() - out, err := b.run(c.Input) + out, err := b.run(c.Input, types.ZeroAddress, nil) if err != nil { t.Fatal(err) } diff --git a/state/runtime/precompiled/bls_agg_sigs_verification.go b/state/runtime/precompiled/bls_agg_sigs_verification.go new file mode 100644 index 0000000000..9e358528b8 --- /dev/null +++ b/state/runtime/precompiled/bls_agg_sigs_verification.go @@ -0,0 +1,107 @@ +package precompiled + +import ( + "errors" + + "github.com/0xPolygon/polygon-edge/chain" + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/state/runtime" + "github.com/0xPolygon/polygon-edge/types" + "github.com/umbracle/ethgo/abi" +) + +var ( + errBLSVerifyAggSignsInputs = errors.New("invalid input") + errBLSVerifyAggSignsInputsLen = errors.New("invalid input length") + errBLSVInvalidSign = errors.New("invalid signature") + errBLSVInvalidPubKeys = errors.New("invalid public keys") + errBLSVInvalidMsg = errors.New("invalid message") + errBLSVInvalidBlsPart = errors.New("bls verification part not in correct format") + errBLSVInvalidPubKeysPart = errors.New("could not find public keys part") + // BlsVerificationABIType is ABI type used for BLS signatures verification. + // It includes BLS public keys and bitmap representing signer validator accounts. + BlsVerificationABIType = abi.MustNewType("tuple(bytes[], bytes)") + // inputDataABIType is the ABI signature of the precompiled contract input data + inputDataABIType = abi.MustNewType("tuple(bytes32, bytes, bytes)") +) + +// blsAggSignsVerification verifies the given aggregated signatures using the default BLS utils functions. +// blsAggSignsVerification returns ABI encoded boolean value depends on validness of the given signatures. +type blsAggSignsVerification struct { +} + +// gas returns the gas required to execute the pre-compiled contract +func (c *blsAggSignsVerification) gas(input []byte, _ *chain.ForksInTime) uint64 { + return 150000 +} + +// Run runs the precompiled contract with the given input. +// Input must be ABI encoded: tuple(bytes, bytes[], bytes) +// Output could be an error or ABI encoded "bool" value +func (c *blsAggSignsVerification) run(input []byte, caller types.Address, host runtime.Host) ([]byte, error) { + rawData, err := abi.Decode(inputDataABIType, input) + if err != nil { + return nil, err + } + + data, ok := rawData.(map[string]interface{}) + if !ok { + return nil, errBLSVerifyAggSignsInputs + } + + if len(data) != 3 { + return nil, errBLSVerifyAggSignsInputsLen + } + + msg, ok := data["0"].([types.HashLength]byte) + if !ok { + return nil, errBLSVInvalidMsg + } + + aggSig, ok := data["1"].([]byte) + if !ok { + return nil, errBLSVInvalidSign + } + + sig, err := bls.UnmarshalSignature(aggSig) + if err != nil { + return nil, errBLSVInvalidSign + } + + blsVerification, ok := data["2"].([]byte) + if !ok { + return nil, errBLSVInvalidPubKeys + } + + decoded, err := BlsVerificationABIType.Decode(blsVerification) + if err != nil { + return nil, err + } + + blsMap, ok := decoded.(map[string]interface{}) + if !ok { + return nil, errBLSVInvalidBlsPart + } + + publicKeys, isOk := blsMap["0"].([][]byte) + if !isOk { + return nil, errBLSVInvalidPubKeysPart + } + + blsPubKeys := make([]*bls.PublicKey, len(publicKeys)) + + for i, pk := range publicKeys { + blsPubKey, err := bls.UnmarshalPublicKey(pk) + if err != nil { + return nil, errBLSVInvalidPubKeys + } + + blsPubKeys[i] = blsPubKey + } + + if sig.VerifyAggregated(blsPubKeys, types.Hash(msg).Bytes()) { + return abiBoolTrue, nil + } + + return abiBoolFalse, nil +} diff --git a/state/runtime/precompiled/bls_agg_sigs_verification_test.go b/state/runtime/precompiled/bls_agg_sigs_verification_test.go new file mode 100644 index 0000000000..8248ba033f --- /dev/null +++ b/state/runtime/precompiled/bls_agg_sigs_verification_test.go @@ -0,0 +1,97 @@ +package precompiled + +import ( + "testing" + + bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo/abi" +) + +func Test_BlsAggSignsVerification(t *testing.T) { + const numberOfKeys = 8 + + b := &blsAggSignsVerification{} + message := []byte("test message to sign") + + pubKeys, signatures := generatePubKeysAndSignature(t, numberOfKeys, message) + + // correct message, correct signers and signature + testCase := generateInput(t, message, pubKeys, signatures) + out, err := b.run(testCase, types.ZeroAddress, nil) + require.NoError(t, err) + assert.Equal(t, abiBoolTrue, out) + + // empty message + testCase = generateInput(t, nil, pubKeys, signatures) + out, err = b.run(testCase, types.ZeroAddress, nil) + require.NoError(t, err) + assert.Equal(t, abiBoolFalse, out) + + // missing signer + testCase = generateInput(t, message, pubKeys[1:], signatures) + out, err = b.run(testCase, types.ZeroAddress, nil) + require.NoError(t, err) + assert.Equal(t, abiBoolFalse, out) + + // missing signature + testCase = generateInput(t, message, pubKeys, signatures[1:]) + out, err = b.run(testCase, types.ZeroAddress, nil) + require.NoError(t, err) + assert.Equal(t, abiBoolFalse, out) + + // invalid signer + testCase = generateInput(t, message, append(pubKeys[2:], pubKeys[0]), signatures[1:]) + out, err = b.run(testCase, types.ZeroAddress, nil) + require.NoError(t, err) + assert.Equal(t, abiBoolFalse, out) + + // invalid signature + testCase = generateInput(t, message, pubKeys[1:], append(signatures[2:], signatures[0])) + out, err = b.run(testCase, types.ZeroAddress, nil) + require.NoError(t, err) + assert.Equal(t, abiBoolFalse, out) +} + +func generatePubKeysAndSignature(t *testing.T, numKeys int, messageRaw []byte) ([][]byte, bls.Signatures) { + t.Helper() + + message := types.BytesToHash(messageRaw) + + validators, err := bls.CreateRandomBlsKeys(numKeys) + require.NoError(t, err) + + pubKeys := make([][]byte, len(validators)) + signatures := make(bls.Signatures, len(validators)) + + for i, validator := range validators { + sign, err := validator.Sign(message[:]) + require.NoError(t, err) + + pubKeys[i] = validator.PublicKey().Marshal() + signatures[i] = sign + } + + return pubKeys, signatures +} + +func generateInput(t *testing.T, messageRaw []byte, publicKeys [][]byte, signatures bls.Signatures) []byte { + t.Helper() + + message := types.BytesToHash(messageRaw) + + aggSig, err := signatures.Aggregate().Marshal() + require.NoError(t, err) + + blsVerificationPart, err := BlsVerificationABIType.Encode( + [2]interface{}{publicKeys, []byte{}}) + require.NoError(t, err) + + encoded, err := abi.Encode([]interface{}{message[:], aggSig, blsVerificationPart}, + abi.MustNewType("tuple(bytes32, bytes, bytes)")) + require.NoError(t, err) + + return encoded +} diff --git a/state/runtime/precompiled/bn256.go b/state/runtime/precompiled/bn256.go index 08687696ef..7181a3a647 100644 --- a/state/runtime/precompiled/bn256.go +++ b/state/runtime/precompiled/bn256.go @@ -5,6 +5,8 @@ import ( "math/big" "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/state/runtime" + "github.com/0xPolygon/polygon-edge/types" bn256 "github.com/umbracle/go-eth-bn256" ) @@ -20,7 +22,7 @@ func (b *bn256Add) gas(input []byte, config *chain.ForksInTime) uint64 { return 500 } -func (b *bn256Add) run(input []byte) ([]byte, error) { +func (b *bn256Add) run(input []byte, _ types.Address, _ runtime.Host) ([]byte, error) { var val []byte b1 := new(bn256.G1) @@ -54,7 +56,7 @@ func (b *bn256Mul) gas(input []byte, config *chain.ForksInTime) uint64 { return 40000 } -func (b *bn256Mul) run(input []byte) ([]byte, error) { +func (b *bn256Mul) run(input []byte, _ types.Address, _ runtime.Host) ([]byte, error) { var v []byte b0 := new(bn256.G1) @@ -73,15 +75,6 @@ func (b *bn256Mul) run(input []byte) ([]byte, error) { return c.Marshal(), nil } -var ( - falseBytes = make([]byte, 32) - trueBytes = make([]byte, 32) -) - -func init() { - trueBytes[31] = 1 -} - type bn256Pairing struct { p *Precompiled } @@ -95,9 +88,9 @@ func (b *bn256Pairing) gas(input []byte, config *chain.ForksInTime) uint64 { return baseGas + pointGas*uint64(len(input)/192) } -func (b *bn256Pairing) run(input []byte) ([]byte, error) { +func (b *bn256Pairing) run(input []byte, _ types.Address, _ runtime.Host) ([]byte, error) { if len(input) == 0 { - return trueBytes, nil + return abiBoolTrue, nil } if len(input)%192 != 0 { @@ -129,8 +122,8 @@ func (b *bn256Pairing) run(input []byte) ([]byte, error) { } if bn256.PairingCheck(ar, br) { - return trueBytes, nil + return abiBoolTrue, nil } - return falseBytes, nil + return abiBoolFalse, nil } diff --git a/state/runtime/precompiled/console.go b/state/runtime/precompiled/console.go new file mode 100644 index 0000000000..eead1c558d --- /dev/null +++ b/state/runtime/precompiled/console.go @@ -0,0 +1,82 @@ +package precompiled + +import ( + _ "embed" + "encoding/hex" + "fmt" + "regexp" + + "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/state/runtime" + "github.com/0xPolygon/polygon-edge/types" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/abi" +) + +//go:embed console.sol +var consoleContract string + +var logOverloads = map[string]*abi.Type{} + +func init() { + rxp := regexp.MustCompile("abi.encodeWithSignature\\(\"log(.*)\"") + matches := rxp.FindAllStringSubmatch(consoleContract, -1) + + for _, match := range matches { + signature := match[1] + + // parse the type of the console call. Note that 'uint' + // objects are defined without bytes (i.e. 256). + typ, err := abi.NewType("tuple" + signature) + if err != nil { + panic(fmt.Errorf("BUG: Failed to parse %s", signature)) + } + + // signature of the call. Use the version without the bytes in 'uint'. + sig := ethgo.Keccak256([]byte("log" + match[1]))[:4] + logOverloads[hex.EncodeToString(sig)] = typ + } +} + +func decodeConsole(input []byte) (val []string) { + sig := hex.EncodeToString(input[:4]) + logSig, ok := logOverloads[sig] + + if !ok { + return + } + + input = input[4:] + raw, err := logSig.Decode(input) + + if err != nil { + return + } + + valuesMap, ok := raw.(map[string]interface{}) + if !ok { + return + } + + val = []string{} + for _, v := range valuesMap { + val = append(val, fmt.Sprint(v)) + } + + return +} + +// console is a debug precompile contract that simulates the `console.sol` functionality +type console struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract +func (c *console) gas(_ []byte, _ *chain.ForksInTime) uint64 { + return 0 +} + +// Run contains the implementation logic of the precompiled contract +func (c *console) run(input []byte, _ types.Address, _ runtime.Host) ([]byte, error) { + fmt.Printf("Console: %v\n", decodeConsole(input)) + + return nil, nil +} diff --git a/state/runtime/precompiled/console.sol b/state/runtime/precompiled/console.sol new file mode 100644 index 0000000000..21168e8b19 --- /dev/null +++ b/state/runtime/precompiled/console.sol @@ -0,0 +1,5157 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.4.22 <0.9.0; + +library console { + address constant CONSOLE_ADDRESS = + address(0x000000000000000000636F6e736F6c652e6c6f67); + + function _sendLogPayload(bytes memory payload) private view { + uint256 payloadLength = payload.length; + address consoleAddress = CONSOLE_ADDRESS; + assembly { + let payloadStart := add(payload, 32) + let r := staticcall( + gas(), + consoleAddress, + payloadStart, + payloadLength, + 0, + 0 + ) + } + } + + function log() internal view { + _sendLogPayload(abi.encodeWithSignature("log()")); + } + + function logInt(int256 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(int)", p0)); + } + + function logUint(uint256 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint)", p0)); + } + + function logString(string memory p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string)", p0)); + } + + function logBool(bool p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool)", p0)); + } + + function logAddress(address p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address)", p0)); + } + + function logBytes(bytes memory p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes)", p0)); + } + + function logBytes1(bytes1 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes1)", p0)); + } + + function logBytes2(bytes2 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes2)", p0)); + } + + function logBytes3(bytes3 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes3)", p0)); + } + + function logBytes4(bytes4 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes4)", p0)); + } + + function logBytes5(bytes5 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes5)", p0)); + } + + function logBytes6(bytes6 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes6)", p0)); + } + + function logBytes7(bytes7 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes7)", p0)); + } + + function logBytes8(bytes8 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes8)", p0)); + } + + function logBytes9(bytes9 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes9)", p0)); + } + + function logBytes10(bytes10 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes10)", p0)); + } + + function logBytes11(bytes11 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes11)", p0)); + } + + function logBytes12(bytes12 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes12)", p0)); + } + + function logBytes13(bytes13 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes13)", p0)); + } + + function logBytes14(bytes14 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes14)", p0)); + } + + function logBytes15(bytes15 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes15)", p0)); + } + + function logBytes16(bytes16 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes16)", p0)); + } + + function logBytes17(bytes17 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes17)", p0)); + } + + function logBytes18(bytes18 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes18)", p0)); + } + + function logBytes19(bytes19 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes19)", p0)); + } + + function logBytes20(bytes20 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes20)", p0)); + } + + function logBytes21(bytes21 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes21)", p0)); + } + + function logBytes22(bytes22 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes22)", p0)); + } + + function logBytes23(bytes23 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes23)", p0)); + } + + function logBytes24(bytes24 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes24)", p0)); + } + + function logBytes25(bytes25 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes25)", p0)); + } + + function logBytes26(bytes26 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes26)", p0)); + } + + function logBytes27(bytes27 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes27)", p0)); + } + + function logBytes28(bytes28 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes28)", p0)); + } + + function logBytes29(bytes29 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes29)", p0)); + } + + function logBytes30(bytes30 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes30)", p0)); + } + + function logBytes31(bytes31 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes31)", p0)); + } + + function logBytes32(bytes32 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes32)", p0)); + } + + function log(uint256 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint)", p0)); + } + + function log(string memory p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string)", p0)); + } + + function log(bool p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool)", p0)); + } + + function log(address p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address)", p0)); + } + + function log(uint256 p0, uint256 p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint,uint)", p0, p1)); + } + + function log(uint256 p0, string memory p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint,string)", p0, p1)); + } + + function log(uint256 p0, bool p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint,bool)", p0, p1)); + } + + function log(uint256 p0, address p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint,address)", p0, p1)); + } + + function log(string memory p0, uint256 p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint)", p0, p1)); + } + + function log(string memory p0, string memory p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string)", p0, p1)); + } + + function log(string memory p0, bool p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool)", p0, p1)); + } + + function log(string memory p0, address p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address)", p0, p1)); + } + + function log(bool p0, uint256 p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint)", p0, p1)); + } + + function log(bool p0, string memory p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string)", p0, p1)); + } + + function log(bool p0, bool p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool)", p0, p1)); + } + + function log(bool p0, address p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address)", p0, p1)); + } + + function log(address p0, uint256 p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint)", p0, p1)); + } + + function log(address p0, string memory p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string)", p0, p1)); + } + + function log(address p0, bool p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool)", p0, p1)); + } + + function log(address p0, address p1) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(address,address)", p0, p1) + ); + } + + function log( + uint256 p0, + uint256 p1, + uint256 p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,uint,uint)", p0, p1, p2) + ); + } + + function log( + uint256 p0, + uint256 p1, + string memory p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,uint,string)", p0, p1, p2) + ); + } + + function log( + uint256 p0, + uint256 p1, + bool p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,uint,bool)", p0, p1, p2) + ); + } + + function log( + uint256 p0, + uint256 p1, + address p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,uint,address)", p0, p1, p2) + ); + } + + function log( + uint256 p0, + string memory p1, + uint256 p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,string,uint)", p0, p1, p2) + ); + } + + function log( + uint256 p0, + string memory p1, + string memory p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,string,string)", p0, p1, p2) + ); + } + + function log( + uint256 p0, + string memory p1, + bool p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,string,bool)", p0, p1, p2) + ); + } + + function log( + uint256 p0, + string memory p1, + address p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,string,address)", p0, p1, p2) + ); + } + + function log( + uint256 p0, + bool p1, + uint256 p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,bool,uint)", p0, p1, p2) + ); + } + + function log( + uint256 p0, + bool p1, + string memory p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,bool,string)", p0, p1, p2) + ); + } + + function log( + uint256 p0, + bool p1, + bool p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,bool,bool)", p0, p1, p2) + ); + } + + function log( + uint256 p0, + bool p1, + address p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,bool,address)", p0, p1, p2) + ); + } + + function log( + uint256 p0, + address p1, + uint256 p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,address,uint)", p0, p1, p2) + ); + } + + function log( + uint256 p0, + address p1, + string memory p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,address,string)", p0, p1, p2) + ); + } + + function log( + uint256 p0, + address p1, + bool p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,address,bool)", p0, p1, p2) + ); + } + + function log( + uint256 p0, + address p1, + address p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,address,address)", p0, p1, p2) + ); + } + + function log( + string memory p0, + uint256 p1, + uint256 p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(string,uint,uint)", p0, p1, p2) + ); + } + + function log( + string memory p0, + uint256 p1, + string memory p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(string,uint,string)", p0, p1, p2) + ); + } + + function log( + string memory p0, + uint256 p1, + bool p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(string,uint,bool)", p0, p1, p2) + ); + } + + function log( + string memory p0, + uint256 p1, + address p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(string,uint,address)", p0, p1, p2) + ); + } + + function log( + string memory p0, + string memory p1, + uint256 p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(string,string,uint)", p0, p1, p2) + ); + } + + function log( + string memory p0, + string memory p1, + string memory p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(string,string,string)", p0, p1, p2) + ); + } + + function log( + string memory p0, + string memory p1, + bool p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(string,string,bool)", p0, p1, p2) + ); + } + + function log( + string memory p0, + string memory p1, + address p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(string,string,address)", p0, p1, p2) + ); + } + + function log( + string memory p0, + bool p1, + uint256 p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(string,bool,uint)", p0, p1, p2) + ); + } + + function log( + string memory p0, + bool p1, + string memory p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(string,bool,string)", p0, p1, p2) + ); + } + + function log( + string memory p0, + bool p1, + bool p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(string,bool,bool)", p0, p1, p2) + ); + } + + function log( + string memory p0, + bool p1, + address p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(string,bool,address)", p0, p1, p2) + ); + } + + function log( + string memory p0, + address p1, + uint256 p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(string,address,uint)", p0, p1, p2) + ); + } + + function log( + string memory p0, + address p1, + string memory p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(string,address,string)", p0, p1, p2) + ); + } + + function log( + string memory p0, + address p1, + bool p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(string,address,bool)", p0, p1, p2) + ); + } + + function log( + string memory p0, + address p1, + address p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(string,address,address)", p0, p1, p2) + ); + } + + function log( + bool p0, + uint256 p1, + uint256 p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,uint,uint)", p0, p1, p2) + ); + } + + function log( + bool p0, + uint256 p1, + string memory p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,uint,string)", p0, p1, p2) + ); + } + + function log( + bool p0, + uint256 p1, + bool p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,uint,bool)", p0, p1, p2) + ); + } + + function log( + bool p0, + uint256 p1, + address p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,uint,address)", p0, p1, p2) + ); + } + + function log( + bool p0, + string memory p1, + uint256 p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,string,uint)", p0, p1, p2) + ); + } + + function log( + bool p0, + string memory p1, + string memory p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,string,string)", p0, p1, p2) + ); + } + + function log( + bool p0, + string memory p1, + bool p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,string,bool)", p0, p1, p2) + ); + } + + function log( + bool p0, + string memory p1, + address p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,string,address)", p0, p1, p2) + ); + } + + function log( + bool p0, + bool p1, + uint256 p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,bool,uint)", p0, p1, p2) + ); + } + + function log( + bool p0, + bool p1, + string memory p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,bool,string)", p0, p1, p2) + ); + } + + function log( + bool p0, + bool p1, + bool p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,bool,bool)", p0, p1, p2) + ); + } + + function log( + bool p0, + bool p1, + address p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,bool,address)", p0, p1, p2) + ); + } + + function log( + bool p0, + address p1, + uint256 p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,address,uint)", p0, p1, p2) + ); + } + + function log( + bool p0, + address p1, + string memory p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,address,string)", p0, p1, p2) + ); + } + + function log( + bool p0, + address p1, + bool p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,address,bool)", p0, p1, p2) + ); + } + + function log( + bool p0, + address p1, + address p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,address,address)", p0, p1, p2) + ); + } + + function log( + address p0, + uint256 p1, + uint256 p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(address,uint,uint)", p0, p1, p2) + ); + } + + function log( + address p0, + uint256 p1, + string memory p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(address,uint,string)", p0, p1, p2) + ); + } + + function log( + address p0, + uint256 p1, + bool p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(address,uint,bool)", p0, p1, p2) + ); + } + + function log( + address p0, + uint256 p1, + address p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(address,uint,address)", p0, p1, p2) + ); + } + + function log( + address p0, + string memory p1, + uint256 p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(address,string,uint)", p0, p1, p2) + ); + } + + function log( + address p0, + string memory p1, + string memory p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(address,string,string)", p0, p1, p2) + ); + } + + function log( + address p0, + string memory p1, + bool p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(address,string,bool)", p0, p1, p2) + ); + } + + function log( + address p0, + string memory p1, + address p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(address,string,address)", p0, p1, p2) + ); + } + + function log( + address p0, + bool p1, + uint256 p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(address,bool,uint)", p0, p1, p2) + ); + } + + function log( + address p0, + bool p1, + string memory p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(address,bool,string)", p0, p1, p2) + ); + } + + function log( + address p0, + bool p1, + bool p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(address,bool,bool)", p0, p1, p2) + ); + } + + function log( + address p0, + bool p1, + address p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(address,bool,address)", p0, p1, p2) + ); + } + + function log( + address p0, + address p1, + uint256 p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(address,address,uint)", p0, p1, p2) + ); + } + + function log( + address p0, + address p1, + string memory p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(address,address,string)", p0, p1, p2) + ); + } + + function log( + address p0, + address p1, + bool p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(address,address,bool)", p0, p1, p2) + ); + } + + function log( + address p0, + address p1, + address p2 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(address,address,address)", p0, p1, p2) + ); + } + + function log( + uint256 p0, + uint256 p1, + uint256 p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,uint,uint,uint)", p0, p1, p2, p3) + ); + } + + function log( + uint256 p0, + uint256 p1, + uint256 p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,uint,uint,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + uint256 p1, + uint256 p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,uint,uint,bool)", p0, p1, p2, p3) + ); + } + + function log( + uint256 p0, + uint256 p1, + uint256 p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,uint,uint,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + uint256 p1, + string memory p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,uint,string,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + uint256 p1, + string memory p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,uint,string,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + uint256 p1, + string memory p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,uint,string,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + uint256 p1, + string memory p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,uint,string,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + uint256 p1, + bool p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,uint,bool,uint)", p0, p1, p2, p3) + ); + } + + function log( + uint256 p0, + uint256 p1, + bool p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,uint,bool,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + uint256 p1, + bool p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,uint,bool,bool)", p0, p1, p2, p3) + ); + } + + function log( + uint256 p0, + uint256 p1, + bool p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,uint,bool,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + uint256 p1, + address p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,uint,address,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + uint256 p1, + address p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,uint,address,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + uint256 p1, + address p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,uint,address,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + uint256 p1, + address p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,uint,address,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + string memory p1, + uint256 p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,string,uint,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + string memory p1, + uint256 p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,string,uint,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + string memory p1, + uint256 p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,string,uint,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + string memory p1, + uint256 p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,string,uint,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + string memory p1, + string memory p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,string,string,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + string memory p1, + string memory p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,string,string,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + string memory p1, + string memory p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,string,string,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + string memory p1, + string memory p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,string,string,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + string memory p1, + bool p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,string,bool,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + string memory p1, + bool p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,string,bool,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + string memory p1, + bool p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,string,bool,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + string memory p1, + bool p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,string,bool,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + string memory p1, + address p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,string,address,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + string memory p1, + address p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,string,address,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + string memory p1, + address p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,string,address,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + string memory p1, + address p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,string,address,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + bool p1, + uint256 p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,bool,uint,uint)", p0, p1, p2, p3) + ); + } + + function log( + uint256 p0, + bool p1, + uint256 p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,bool,uint,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + bool p1, + uint256 p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,bool,uint,bool)", p0, p1, p2, p3) + ); + } + + function log( + uint256 p0, + bool p1, + uint256 p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,bool,uint,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + bool p1, + string memory p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,bool,string,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + bool p1, + string memory p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,bool,string,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + bool p1, + string memory p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,bool,string,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + bool p1, + string memory p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,bool,string,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + bool p1, + bool p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,bool,bool,uint)", p0, p1, p2, p3) + ); + } + + function log( + uint256 p0, + bool p1, + bool p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,bool,bool,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + bool p1, + bool p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(uint,bool,bool,bool)", p0, p1, p2, p3) + ); + } + + function log( + uint256 p0, + bool p1, + bool p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,bool,bool,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + bool p1, + address p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,bool,address,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + bool p1, + address p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,bool,address,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + bool p1, + address p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,bool,address,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + bool p1, + address p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,bool,address,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + address p1, + uint256 p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,address,uint,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + address p1, + uint256 p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,address,uint,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + address p1, + uint256 p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,address,uint,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + address p1, + uint256 p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,address,uint,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + address p1, + string memory p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,address,string,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + address p1, + string memory p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,address,string,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + address p1, + string memory p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,address,string,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + address p1, + string memory p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,address,string,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + address p1, + bool p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,address,bool,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + address p1, + bool p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,address,bool,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + address p1, + bool p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,address,bool,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + address p1, + bool p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,address,bool,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + address p1, + address p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,address,address,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + address p1, + address p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,address,address,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + address p1, + address p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,address,address,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + uint256 p0, + address p1, + address p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(uint,address,address,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + uint256 p1, + uint256 p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,uint,uint,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + uint256 p1, + uint256 p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,uint,uint,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + uint256 p1, + uint256 p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,uint,uint,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + uint256 p1, + uint256 p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,uint,uint,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + uint256 p1, + string memory p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,uint,string,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + uint256 p1, + string memory p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,uint,string,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + uint256 p1, + string memory p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,uint,string,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + uint256 p1, + string memory p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,uint,string,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + uint256 p1, + bool p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,uint,bool,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + uint256 p1, + bool p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,uint,bool,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + uint256 p1, + bool p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,uint,bool,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + uint256 p1, + bool p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,uint,bool,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + uint256 p1, + address p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,uint,address,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + uint256 p1, + address p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,uint,address,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + uint256 p1, + address p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,uint,address,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + uint256 p1, + address p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,uint,address,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + string memory p1, + uint256 p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,string,uint,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + string memory p1, + uint256 p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,string,uint,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + string memory p1, + uint256 p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,string,uint,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + string memory p1, + uint256 p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,string,uint,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + string memory p1, + string memory p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,string,string,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + string memory p1, + string memory p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,string,string,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + string memory p1, + string memory p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,string,string,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + string memory p1, + string memory p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,string,string,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + string memory p1, + bool p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,string,bool,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + string memory p1, + bool p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,string,bool,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + string memory p1, + bool p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,string,bool,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + string memory p1, + bool p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,string,bool,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + string memory p1, + address p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,string,address,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + string memory p1, + address p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,string,address,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + string memory p1, + address p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,string,address,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + string memory p1, + address p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,string,address,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + bool p1, + uint256 p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,bool,uint,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + bool p1, + uint256 p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,bool,uint,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + bool p1, + uint256 p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,bool,uint,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + bool p1, + uint256 p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,bool,uint,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + bool p1, + string memory p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,bool,string,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + bool p1, + string memory p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,bool,string,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + bool p1, + string memory p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,bool,string,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + bool p1, + string memory p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,bool,string,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + bool p1, + bool p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,bool,bool,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + bool p1, + bool p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,bool,bool,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + bool p1, + bool p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,bool,bool,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + bool p1, + bool p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,bool,bool,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + bool p1, + address p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,bool,address,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + bool p1, + address p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,bool,address,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + bool p1, + address p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,bool,address,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + bool p1, + address p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,bool,address,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + address p1, + uint256 p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,address,uint,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + address p1, + uint256 p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,address,uint,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + address p1, + uint256 p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,address,uint,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + address p1, + uint256 p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,address,uint,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + address p1, + string memory p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,address,string,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + address p1, + string memory p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,address,string,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + address p1, + string memory p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,address,string,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + address p1, + string memory p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,address,string,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + address p1, + bool p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,address,bool,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + address p1, + bool p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,address,bool,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + address p1, + bool p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,address,bool,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + address p1, + bool p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,address,bool,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + address p1, + address p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,address,address,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + address p1, + address p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,address,address,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + address p1, + address p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,address,address,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + string memory p0, + address p1, + address p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(string,address,address,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + uint256 p1, + uint256 p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,uint,uint,uint)", p0, p1, p2, p3) + ); + } + + function log( + bool p0, + uint256 p1, + uint256 p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,uint,uint,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + uint256 p1, + uint256 p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,uint,uint,bool)", p0, p1, p2, p3) + ); + } + + function log( + bool p0, + uint256 p1, + uint256 p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,uint,uint,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + uint256 p1, + string memory p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,uint,string,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + uint256 p1, + string memory p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,uint,string,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + uint256 p1, + string memory p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,uint,string,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + uint256 p1, + string memory p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,uint,string,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + uint256 p1, + bool p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,uint,bool,uint)", p0, p1, p2, p3) + ); + } + + function log( + bool p0, + uint256 p1, + bool p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,uint,bool,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + uint256 p1, + bool p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,uint,bool,bool)", p0, p1, p2, p3) + ); + } + + function log( + bool p0, + uint256 p1, + bool p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,uint,bool,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + uint256 p1, + address p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,uint,address,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + uint256 p1, + address p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,uint,address,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + uint256 p1, + address p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,uint,address,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + uint256 p1, + address p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,uint,address,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + string memory p1, + uint256 p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,string,uint,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + string memory p1, + uint256 p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,string,uint,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + string memory p1, + uint256 p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,string,uint,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + string memory p1, + uint256 p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,string,uint,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + string memory p1, + string memory p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,string,string,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + string memory p1, + string memory p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,string,string,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + string memory p1, + string memory p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,string,string,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + string memory p1, + string memory p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,string,string,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + string memory p1, + bool p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,string,bool,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + string memory p1, + bool p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,string,bool,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + string memory p1, + bool p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,string,bool,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + string memory p1, + bool p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,string,bool,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + string memory p1, + address p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,string,address,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + string memory p1, + address p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,string,address,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + string memory p1, + address p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,string,address,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + string memory p1, + address p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,string,address,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + bool p1, + uint256 p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,bool,uint,uint)", p0, p1, p2, p3) + ); + } + + function log( + bool p0, + bool p1, + uint256 p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,bool,uint,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + bool p1, + uint256 p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,bool,uint,bool)", p0, p1, p2, p3) + ); + } + + function log( + bool p0, + bool p1, + uint256 p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,bool,uint,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + bool p1, + string memory p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,bool,string,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + bool p1, + string memory p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,bool,string,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + bool p1, + string memory p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,bool,string,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + bool p1, + string memory p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,bool,string,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + bool p1, + bool p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,bool,bool,uint)", p0, p1, p2, p3) + ); + } + + function log( + bool p0, + bool p1, + bool p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,bool,bool,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + bool p1, + bool p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature("log(bool,bool,bool,bool)", p0, p1, p2, p3) + ); + } + + function log( + bool p0, + bool p1, + bool p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,bool,bool,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + bool p1, + address p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,bool,address,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + bool p1, + address p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,bool,address,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + bool p1, + address p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,bool,address,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + bool p1, + address p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,bool,address,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + address p1, + uint256 p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,address,uint,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + address p1, + uint256 p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,address,uint,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + address p1, + uint256 p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,address,uint,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + address p1, + uint256 p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,address,uint,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + address p1, + string memory p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,address,string,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + address p1, + string memory p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,address,string,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + address p1, + string memory p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,address,string,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + address p1, + string memory p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,address,string,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + address p1, + bool p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,address,bool,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + address p1, + bool p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,address,bool,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + address p1, + bool p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,address,bool,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + address p1, + bool p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,address,bool,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + address p1, + address p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,address,address,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + address p1, + address p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,address,address,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + address p1, + address p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,address,address,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + bool p0, + address p1, + address p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(bool,address,address,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + uint256 p1, + uint256 p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,uint,uint,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + uint256 p1, + uint256 p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,uint,uint,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + uint256 p1, + uint256 p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,uint,uint,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + uint256 p1, + uint256 p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,uint,uint,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + uint256 p1, + string memory p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,uint,string,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + uint256 p1, + string memory p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,uint,string,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + uint256 p1, + string memory p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,uint,string,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + uint256 p1, + string memory p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,uint,string,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + uint256 p1, + bool p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,uint,bool,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + uint256 p1, + bool p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,uint,bool,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + uint256 p1, + bool p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,uint,bool,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + uint256 p1, + bool p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,uint,bool,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + uint256 p1, + address p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,uint,address,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + uint256 p1, + address p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,uint,address,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + uint256 p1, + address p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,uint,address,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + uint256 p1, + address p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,uint,address,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + string memory p1, + uint256 p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,string,uint,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + string memory p1, + uint256 p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,string,uint,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + string memory p1, + uint256 p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,string,uint,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + string memory p1, + uint256 p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,string,uint,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + string memory p1, + string memory p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,string,string,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + string memory p1, + string memory p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,string,string,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + string memory p1, + string memory p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,string,string,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + string memory p1, + string memory p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,string,string,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + string memory p1, + bool p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,string,bool,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + string memory p1, + bool p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,string,bool,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + string memory p1, + bool p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,string,bool,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + string memory p1, + bool p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,string,bool,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + string memory p1, + address p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,string,address,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + string memory p1, + address p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,string,address,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + string memory p1, + address p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,string,address,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + string memory p1, + address p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,string,address,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + bool p1, + uint256 p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,bool,uint,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + bool p1, + uint256 p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,bool,uint,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + bool p1, + uint256 p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,bool,uint,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + bool p1, + uint256 p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,bool,uint,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + bool p1, + string memory p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,bool,string,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + bool p1, + string memory p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,bool,string,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + bool p1, + string memory p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,bool,string,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + bool p1, + string memory p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,bool,string,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + bool p1, + bool p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,bool,bool,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + bool p1, + bool p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,bool,bool,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + bool p1, + bool p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,bool,bool,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + bool p1, + bool p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,bool,bool,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + bool p1, + address p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,bool,address,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + bool p1, + address p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,bool,address,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + bool p1, + address p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,bool,address,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + bool p1, + address p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,bool,address,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + address p1, + uint256 p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,address,uint,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + address p1, + uint256 p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,address,uint,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + address p1, + uint256 p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,address,uint,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + address p1, + uint256 p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,address,uint,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + address p1, + string memory p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,address,string,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + address p1, + string memory p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,address,string,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + address p1, + string memory p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,address,string,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + address p1, + string memory p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,address,string,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + address p1, + bool p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,address,bool,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + address p1, + bool p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,address,bool,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + address p1, + bool p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,address,bool,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + address p1, + bool p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,address,bool,address)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + address p1, + address p2, + uint256 p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,address,address,uint)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + address p1, + address p2, + string memory p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,address,address,string)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + address p1, + address p2, + bool p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,address,address,bool)", + p0, + p1, + p2, + p3 + ) + ); + } + + function log( + address p0, + address p1, + address p2, + address p3 + ) internal view { + _sendLogPayload( + abi.encodeWithSignature( + "log(address,address,address,address)", + p0, + p1, + p2, + p3 + ) + ); + } +} diff --git a/state/runtime/precompiled/modexp.go b/state/runtime/precompiled/modexp.go index 854e7f4b42..c4ef6a6873 100644 --- a/state/runtime/precompiled/modexp.go +++ b/state/runtime/precompiled/modexp.go @@ -6,6 +6,8 @@ import ( "math" "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/state/runtime" + "github.com/0xPolygon/polygon-edge/types" ) type modExp struct { @@ -137,7 +139,7 @@ func (m *modExp) gas(input []byte, config *chain.ForksInTime) uint64 { return gasCost.Uint64() } -func (m *modExp) run(input []byte) ([]byte, error) { +func (m *modExp) run(input []byte, _ types.Address, _ runtime.Host) ([]byte, error) { // get the lengths var baseLen, exponentLen, modulusLen uint64 diff --git a/state/runtime/precompiled/native_transfer.go b/state/runtime/precompiled/native_transfer.go new file mode 100644 index 0000000000..1ad195e6d4 --- /dev/null +++ b/state/runtime/precompiled/native_transfer.go @@ -0,0 +1,38 @@ +package precompiled + +import ( + "math/big" + + "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/state/runtime" + "github.com/0xPolygon/polygon-edge/types" +) + +type nativeTransfer struct{} + +func (c *nativeTransfer) gas(input []byte, _ *chain.ForksInTime) uint64 { + return 21000 +} + +func (c *nativeTransfer) run(input []byte, caller types.Address, host runtime.Host) ([]byte, error) { + if len(input) < 96 { + return abiBoolFalse, runtime.ErrInvalidInputData + } + + // check if caller is native token contract + if caller != contracts.NativeTokenContract { + return abiBoolFalse, runtime.ErrUnauthorizedCaller + } + + from := types.BytesToAddress(input[0:32]) + to := types.BytesToAddress(input[32:64]) + amount := new(big.Int).SetBytes(input[64:96]) + + // state changes + if err := host.Transfer(from, to, amount); err != nil { + return abiBoolFalse, err + } + + return abiBoolTrue, nil +} diff --git a/state/runtime/precompiled/native_transfer_test.go b/state/runtime/precompiled/native_transfer_test.go new file mode 100644 index 0000000000..94a451bfa5 --- /dev/null +++ b/state/runtime/precompiled/native_transfer_test.go @@ -0,0 +1,160 @@ +package precompiled + +import ( + "math/big" + "testing" + + "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/state/runtime" + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo/abi" +) + +func Test_NativeTransferPrecompile(t *testing.T) { + var ( + sender = types.Address{0x1} + receiver = types.Address{0x2} + ) + + contract := &nativeTransfer{} + abiType := abi.MustNewType("tuple(address, address, uint256)") + run := func(caller, from, to types.Address, amount *big.Int, host runtime.Host) error { + input, err := abiType.Encode([]interface{}{from, to, amount}) + require.NoError(t, err) + + _, err = contract.run(input, caller, host) + + return err + } + + t.Run("Invalid input", func(t *testing.T) { + _, err := contract.run([]byte{}, types.Address{}, nil) + require.ErrorIs(t, err, runtime.ErrInvalidInputData) + }) + t.Run("Caller not authorized", func(t *testing.T) { + err := run(types.ZeroAddress, sender, receiver, big.NewInt(10), nil) + require.ErrorIs(t, err, runtime.ErrUnauthorizedCaller) + }) + t.Run("Insufficient balance", func(t *testing.T) { + err := run(contracts.NativeTokenContract, sender, receiver, big.NewInt(10), newDummyHost()) + require.ErrorIs(t, err, runtime.ErrInsufficientBalance) + }) + t.Run("Correct transfer", func(t *testing.T) { + host := newDummyHost() + host.AddBalance(sender, big.NewInt(1000)) + + err := run(contracts.NativeTokenContract, sender, receiver, big.NewInt(100), host) + require.NoError(t, err) + require.Equal(t, big.NewInt(900), host.GetBalance(sender)) + require.Equal(t, big.NewInt(100), host.GetBalance(receiver)) + }) +} + +// d dummyHost +var _ runtime.Host = (*dummyHost)(nil) + +type dummyHost struct { + balances map[types.Address]*big.Int +} + +func newDummyHost() *dummyHost { + return &dummyHost{ + balances: map[types.Address]*big.Int{}, + } +} + +func (d dummyHost) AddBalance(addr types.Address, balance *big.Int) { + existingBalance := d.GetBalance(addr) + existingBalance = new(big.Int).Add(existingBalance, balance) + d.balances[addr] = existingBalance +} + +func (d dummyHost) AccountExists(addr types.Address) bool { + panic("not implemented") +} + +func (d dummyHost) GetStorage(addr types.Address, key types.Hash) types.Hash { + panic("not implemented") +} + +func (d dummyHost) SetStorage(addr types.Address, key types.Hash, value types.Hash, config *chain.ForksInTime) runtime.StorageStatus { + panic("not implemented") +} + +func (d dummyHost) GetBalance(addr types.Address) *big.Int { + balance, exists := d.balances[addr] + if !exists { + return big.NewInt(0) + } + + return balance +} + +func (d dummyHost) GetCodeSize(addr types.Address) int { + panic("not implemented") +} + +func (d dummyHost) GetCodeHash(addr types.Address) types.Hash { + panic("not implemented") +} + +func (d dummyHost) GetCode(addr types.Address) []byte { + panic("not implemented") +} + +func (d dummyHost) Selfdestruct(addr types.Address, beneficiary types.Address) { + panic("not implemented") +} + +func (d dummyHost) GetTxContext() runtime.TxContext { + panic("not implemented") +} + +func (d dummyHost) GetBlockHash(number int64) types.Hash { + panic("not implemented") +} + +func (d dummyHost) EmitLog(addr types.Address, topics []types.Hash, data []byte) { + panic("not implemented") +} + +func (d dummyHost) Callx(_ *runtime.Contract, _ runtime.Host) *runtime.ExecutionResult { + panic("not implemented") +} + +func (d dummyHost) Empty(addr types.Address) bool { + panic("not implemented") +} + +func (d dummyHost) GetNonce(addr types.Address) uint64 { + panic("not implemented") +} + +func (d dummyHost) Transfer(from types.Address, to types.Address, amount *big.Int) error { + if d.balances == nil { + d.balances = map[types.Address]*big.Int{} + } + + senderBalance := d.GetBalance(from) + if senderBalance.Cmp(amount) < 0 { + return runtime.ErrInsufficientBalance + } + + senderBalance = new(big.Int).Sub(senderBalance, amount) + d.balances[from] = senderBalance + + receiverBalance := d.GetBalance(to) + d.balances[to] = new(big.Int).Add(receiverBalance, amount) + + return nil +} + +func (d dummyHost) GetTracer() runtime.VMTracer { + return nil +} + +func (d dummyHost) GetRefund() uint64 { + return 0 +} diff --git a/state/runtime/precompiled/precompiled.go b/state/runtime/precompiled/precompiled.go index 3eb9a7b762..3e18ec45e7 100644 --- a/state/runtime/precompiled/precompiled.go +++ b/state/runtime/precompiled/precompiled.go @@ -4,15 +4,25 @@ import ( "encoding/binary" "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/contracts" + "github.com/0xPolygon/polygon-edge/helper/common" "github.com/0xPolygon/polygon-edge/state/runtime" "github.com/0xPolygon/polygon-edge/types" + "github.com/umbracle/ethgo/abi" ) var _ runtime.Runtime = &Precompiled{} +var ( + // abiBoolTrue is ABI encoded true boolean value + abiBoolTrue = abiBoolMustEncode(true) + // abiBoolFalse is ABI encoded false boolean value + abiBoolFalse = abiBoolMustEncode(false) +) + type contract interface { gas(input []byte, config *chain.ForksInTime) uint64 - run(input []byte) ([]byte, error) + run(input []byte, caller types.Address, host runtime.Host) ([]byte, error) } // Precompiled is the runtime for the precompiled contracts @@ -43,6 +53,15 @@ func (p *Precompiled) setupContracts() { // Istanbul fork p.register("9", &blake2f{p}) + + // Native transfer precompile + p.register(contracts.NativeTransferPrecompile.String(), &nativeTransfer{}) + + // BLS aggregated signatures verification precompile + p.register(contracts.BLSAggSigsVerificationPrecompile.String(), &blsAggSignsVerification{}) + + // Console precompile + p.register(contracts.ConsolePrecompile.String(), &console{}) } func (p *Precompiled) register(addrStr string, b contract) { @@ -94,7 +113,7 @@ func (p *Precompiled) Name() string { } // Run runs an execution -func (p *Precompiled) Run(c *runtime.Contract, _ runtime.Host, config *chain.ForksInTime) *runtime.ExecutionResult { +func (p *Precompiled) Run(c *runtime.Contract, host runtime.Host, config *chain.ForksInTime) *runtime.ExecutionResult { contract := p.contracts[c.CodeAddress] gasCost := contract.gas(c.Input, config) @@ -107,7 +126,7 @@ func (p *Precompiled) Run(c *runtime.Contract, _ runtime.Host, config *chain.For } c.Gas = c.Gas - gasCost - returnValue, err := contract.run(c.Input) + returnValue, err := contract.run(c.Input, c.Caller, host) result := &runtime.ExecutionResult{ ReturnValue: returnValue, @@ -139,7 +158,7 @@ func (p *Precompiled) leftPad(buf []byte, n int) []byte { } func (p *Precompiled) get(input []byte, size int) ([]byte, []byte) { - p.buf = extendByteSlice(p.buf, size) + p.buf = common.ExtendByteSlice(p.buf, size) n := size if len(input) < n { @@ -169,11 +188,13 @@ func (p *Precompiled) getUint64(input []byte) (uint64, []byte) { return num, input } -func extendByteSlice(b []byte, needLen int) []byte { - b = b[:cap(b)] - if n := needLen - cap(b); n > 0 { - b = append(b, make([]byte, n)...) +// abiBoolMustEncode encodes the given value using the given ABI type. +// panics if there is an error occurred. +func abiBoolMustEncode(v bool) []byte { + raw, err := abi.MustNewType("bool").Encode(v) + if err != nil { + panic(err) } - return b[:needLen] + return raw } diff --git a/state/runtime/runtime.go b/state/runtime/runtime.go index d1df1d487c..253bfd2c6a 100644 --- a/state/runtime/runtime.go +++ b/state/runtime/runtime.go @@ -71,6 +71,7 @@ type Host interface { Callx(*Contract, Host) *ExecutionResult Empty(addr types.Address) bool GetNonce(addr types.Address) uint64 + Transfer(from types.Address, to types.Address, amount *big.Int) error GetTracer() VMTracer GetRefund() uint64 } @@ -101,10 +102,11 @@ type VMTracer interface { // ExecutionResult includes all output after executing given evm // message no matter the execution itself is successful or not. type ExecutionResult struct { - ReturnValue []byte // Returned data from the runtime (function result or data supplied with revert opcode) - GasLeft uint64 // Total gas left as result of execution - GasUsed uint64 // Total gas used as result of execution - Err error // Any error encountered during the execution, listed below + ReturnValue []byte // Returned data from the runtime (function result or data supplied with revert opcode) + GasLeft uint64 // Total gas left as result of execution + GasUsed uint64 // Total gas used as result of execution + Err error // Any error encountered during the execution, listed below + Address types.Address // Contract address } func (r *ExecutionResult) Succeeded() bool { return r.Err == nil } @@ -134,6 +136,8 @@ var ( ErrDepth = errors.New("max call depth exceeded") ErrExecutionReverted = errors.New("execution was reverted") ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas") + ErrUnauthorizedCaller = errors.New("unauthorized caller") + ErrInvalidInputData = errors.New("invalid input data") ) type CallType int diff --git a/state/transition_test.go b/state/transition_test.go index 87ae04568a..de94c84cb0 100644 --- a/state/transition_test.go +++ b/state/transition_test.go @@ -141,7 +141,7 @@ func TestTransfer(t *testing.T) { transition := newTestTransition(tt.preState) amount := big.NewInt(tt.amount) - err := transition.transfer(tt.from, tt.to, amount) + err := transition.Transfer(tt.from, tt.to, amount) assert.Equal(t, tt.expectedErr, err) if err == nil { diff --git a/syncer/syncer.go b/syncer/syncer.go index 88e7899af4..6a3c32ffed 100644 --- a/syncer/syncer.go +++ b/syncer/syncer.go @@ -159,7 +159,7 @@ func (s *syncer) HasSyncPeer() bool { } // Sync syncs block with the best peer until callback returns true -func (s *syncer) Sync(callback func(*types.Block) bool) error { +func (s *syncer) Sync(callback func(*types.FullBlock) bool) error { localLatest := s.blockchain.Header().Number skipList := make(map[peer.ID]bool) @@ -208,7 +208,7 @@ func (s *syncer) Sync(callback func(*types.Block) bool) error { } // bulkSyncWithPeer syncs block with a given peer -func (s *syncer) bulkSyncWithPeer(peerID peer.ID, newBlockCallback func(*types.Block) bool) (uint64, bool, error) { +func (s *syncer) bulkSyncWithPeer(peerID peer.ID, newBlockCallback func(*types.FullBlock) bool) (uint64, bool, error) { localLatest := s.blockchain.Header().Number shouldTerminate := false @@ -238,15 +238,16 @@ func (s *syncer) bulkSyncWithPeer(peerID peer.ID, newBlockCallback func(*types.B continue } - if err := s.blockchain.VerifyFinalizedBlock(block); err != nil { + fullBlock, err := s.blockchain.VerifyFinalizedBlock(block) + if err != nil { return lastReceivedNumber, false, fmt.Errorf("unable to verify block, %w", err) } - if err := s.blockchain.WriteBlock(block, syncerName); err != nil { + if err := s.blockchain.WriteFullBlock(fullBlock, syncerName); err != nil { return lastReceivedNumber, false, fmt.Errorf("failed to write block while bulk syncing: %w", err) } - shouldTerminate = newBlockCallback(block) + shouldTerminate = newBlockCallback(fullBlock) lastReceivedNumber = block.Number() case <-time.After(s.blockTimeout): diff --git a/syncer/syncer_test.go b/syncer/syncer_test.go index a026ecc171..c97f615479 100644 --- a/syncer/syncer_test.go +++ b/syncer/syncer_test.go @@ -39,8 +39,9 @@ type mockBlockchain struct { subscription blockchain.Subscription headerHandler func() *types.Header getBlockByNumberHandler func(uint64, bool) (*types.Block, bool) - verifyFinalizedBlockHandler func(*types.Block) error + verifyFinalizedBlockHandler func(*types.Block) (*types.FullBlock, error) writeBlockHandler func(*types.Block) error + writeFullBlockHandler func(*types.FullBlock) error } func (m *mockBlockchain) SubscribeEvents() blockchain.Subscription { @@ -55,7 +56,7 @@ func (m *mockBlockchain) GetBlockByNumber(number uint64, full bool) (*types.Bloc return m.getBlockByNumberHandler(number, full) } -func (m *mockBlockchain) VerifyFinalizedBlock(b *types.Block) error { +func (m *mockBlockchain) VerifyFinalizedBlock(b *types.Block) (*types.FullBlock, error) { return m.verifyFinalizedBlockHandler(b) } @@ -63,6 +64,10 @@ func (m *mockBlockchain) WriteBlock(b *types.Block, s string) error { return m.writeBlockHandler(b) } +func (m *mockBlockchain) WriteFullBlock(b *types.FullBlock, s string) error { + return m.writeFullBlockHandler(b) +} + func newSimpleHeaderHandler(num uint64) func() *types.Header { return func() *types.Header { return &types.Header{ @@ -496,7 +501,7 @@ func TestSync(t *testing.T) { // local beginningHeight uint64 - createBlockCallback func() func(*types.Block) bool + createBlockCallback func() func(*types.FullBlock) bool // peers peerStatuses []*NoForkPeer @@ -506,7 +511,7 @@ func TestSync(t *testing.T) { // handlers // a function to return a callback to use closure - createVerifyFinalizedBlockHandler func() func(*types.Block) error + createVerifyFinalizedBlockHandler func() func(*types.Block) (*types.FullBlock, error) // results blocks []*types.Block @@ -517,9 +522,9 @@ func TestSync(t *testing.T) { { name: "should sync blocks to the latest successfully", beginningHeight: 0, - createBlockCallback: func() func(*types.Block) bool { - return func(b *types.Block) bool { - return b.Number() >= 10 + createBlockCallback: func() func(*types.FullBlock) bool { + return func(b *types.FullBlock) bool { + return b.Block.Number() >= 10 } }, peerStatuses: []*NoForkPeer{ @@ -533,9 +538,9 @@ func TestSync(t *testing.T) { peerBlocksCh: map[peer.ID]<-chan *types.Block{ peer.ID("A"): blocksToCh(blocks[:10], 0), }, - createVerifyFinalizedBlockHandler: func() func(*types.Block) error { - return func(b *types.Block) error { - return nil + createVerifyFinalizedBlockHandler: func() func(*types.Block) (*types.FullBlock, error) { + return func(b *types.Block) (*types.FullBlock, error) { + return &types.FullBlock{Block: b}, nil } }, blocks: blocks[:10], @@ -547,9 +552,9 @@ func TestSync(t *testing.T) { { name: "should sync blocks with multiple peers", beginningHeight: 0, - createBlockCallback: func() func(*types.Block) bool { - return func(b *types.Block) bool { - return b.Number() >= 10 + createBlockCallback: func() func(*types.FullBlock) bool { + return func(b *types.FullBlock) bool { + return b.Block.Number() >= 10 } }, peerStatuses: []*NoForkPeer{ @@ -569,19 +574,19 @@ func TestSync(t *testing.T) { peer.ID("A"): blocksToCh(blocks[:10], 0), peer.ID("B"): blocksToCh(blocks[4:10], 0), }, - createVerifyFinalizedBlockHandler: func() func(*types.Block) error { + createVerifyFinalizedBlockHandler: func() func(*types.Block) (*types.FullBlock, error) { count := 0 - return func(b *types.Block) error { + return func(b *types.Block) (*types.FullBlock, error) { if b.Number() == 5 { count++ if count == 1 { - return errors.New("block verification failed") + return nil, errors.New("block verification failed") } } - return nil + return &types.FullBlock{Block: b}, nil } }, blocks: blocks[:10], @@ -608,9 +613,9 @@ func TestSync(t *testing.T) { &mockBlockchain{ headerHandler: newSimpleHeaderHandler(latestBlockNumber), verifyFinalizedBlockHandler: test.createVerifyFinalizedBlockHandler(), - writeBlockHandler: func(b *types.Block) error { - syncedBlocks = append(syncedBlocks, b) - latestBlockNumber = b.Number() + writeFullBlockHandler: func(b *types.FullBlock) error { + syncedBlocks = append(syncedBlocks, b.Block) + latestBlockNumber = b.Block.Number() return nil }, @@ -681,14 +686,14 @@ func Test_bulkSyncWithPeer(t *testing.T) { // local beginningHeight uint64 blockTimeout time.Duration - blockCallback func(*types.Block) bool + blockCallback func(*types.FullBlock) bool // peers getBlocksHandler func(id peer.ID, start uint64, timeoutPerBlock time.Duration) (<-chan *types.Block, error) // handlers - verifyFinalizedBlockHandler func(*types.Block) error - writeBlockHandler func(*types.Block) error + verifyFinalizedBlockHandler func(*types.Block) (*types.FullBlock, error) + writeFullBlockHandler func(*types.FullBlock) error // results blocks []*types.Block @@ -700,16 +705,16 @@ func Test_bulkSyncWithPeer(t *testing.T) { name: "should sync blocks to the latest successfully", beginningHeight: 0, blockTimeout: time.Second, - blockCallback: func(b *types.Block) bool { + blockCallback: func(b *types.FullBlock) bool { return false }, getBlocksHandler: func(id peer.ID, start uint64, _ time.Duration) (<-chan *types.Block, error) { return blocksToCh(blocks[:10], 0), nil }, - verifyFinalizedBlockHandler: func(b *types.Block) error { - return nil + verifyFinalizedBlockHandler: func(b *types.Block) (*types.FullBlock, error) { + return &types.FullBlock{Block: b}, nil }, - writeBlockHandler: func(b *types.Block) error { + writeFullBlockHandler: func(b *types.FullBlock) error { return nil }, blocks: blocks[:10], @@ -721,16 +726,16 @@ func Test_bulkSyncWithPeer(t *testing.T) { name: "should return error if GetBlocks returns error", beginningHeight: 0, blockTimeout: time.Second, - blockCallback: func(b *types.Block) bool { + blockCallback: func(b *types.FullBlock) bool { return false }, getBlocksHandler: func(id peer.ID, start uint64, _ time.Duration) (<-chan *types.Block, error) { return nil, errPeerNoResponse }, - verifyFinalizedBlockHandler: func(b *types.Block) error { - return nil + verifyFinalizedBlockHandler: func(b *types.Block) (*types.FullBlock, error) { + return &types.FullBlock{Block: b}, nil }, - writeBlockHandler: func(b *types.Block) error { + writeFullBlockHandler: func(b *types.FullBlock) error { return nil }, blocks: []*types.Block{}, @@ -742,20 +747,20 @@ func Test_bulkSyncWithPeer(t *testing.T) { name: "should return error if verification is failed", beginningHeight: 0, blockTimeout: time.Second, - blockCallback: func(b *types.Block) bool { + blockCallback: func(b *types.FullBlock) bool { return false }, getBlocksHandler: func(id peer.ID, start uint64, _ time.Duration) (<-chan *types.Block, error) { return blocksToCh(blocks[:10], 0), nil }, - verifyFinalizedBlockHandler: func(b *types.Block) error { + verifyFinalizedBlockHandler: func(b *types.Block) (*types.FullBlock, error) { if b.Number() > 5 { - return errInvalidBlock + return nil, errInvalidBlock } - return nil + return &types.FullBlock{Block: b}, nil }, - writeBlockHandler: func(b *types.Block) error { + writeFullBlockHandler: func(b *types.FullBlock) error { return nil }, blocks: blocks[:5], @@ -767,17 +772,17 @@ func Test_bulkSyncWithPeer(t *testing.T) { name: "should return error if block insertion is failed", beginningHeight: 0, blockTimeout: time.Second, - blockCallback: func(b *types.Block) bool { + blockCallback: func(b *types.FullBlock) bool { return false }, getBlocksHandler: func(id peer.ID, start uint64, _ time.Duration) (<-chan *types.Block, error) { return blocksToCh(blocks[:10], 0), nil }, - verifyFinalizedBlockHandler: func(b *types.Block) error { - return nil + verifyFinalizedBlockHandler: func(b *types.Block) (*types.FullBlock, error) { + return &types.FullBlock{Block: b}, nil }, - writeBlockHandler: func(b *types.Block) error { - if b.Number() > 5 { + writeFullBlockHandler: func(b *types.FullBlock) error { + if b.Block.Number() > 5 { return errBlockInsertionFailed } @@ -792,16 +797,16 @@ func Test_bulkSyncWithPeer(t *testing.T) { name: "should return error in case of timeout", beginningHeight: 0, blockTimeout: 500 * time.Millisecond, - blockCallback: func(b *types.Block) bool { + blockCallback: func(b *types.FullBlock) bool { return false }, getBlocksHandler: func(id peer.ID, start uint64, _ time.Duration) (<-chan *types.Block, error) { return blocksToCh(blocks[:10], time.Second*1), nil }, - verifyFinalizedBlockHandler: func(b *types.Block) error { - return nil + verifyFinalizedBlockHandler: func(b *types.Block) (*types.FullBlock, error) { + return &types.FullBlock{Block: b}, nil }, - writeBlockHandler: func(b *types.Block) error { + writeFullBlockHandler: func(b *types.FullBlock) error { return nil }, blocks: []*types.Block{}, @@ -825,12 +830,12 @@ func Test_bulkSyncWithPeer(t *testing.T) { &mockBlockchain{ headerHandler: newSimpleHeaderHandler(test.beginningHeight), verifyFinalizedBlockHandler: test.verifyFinalizedBlockHandler, - writeBlockHandler: func(b *types.Block) error { - if err := test.writeBlockHandler(b); err != nil { + writeFullBlockHandler: func(b *types.FullBlock) error { + if err := test.writeFullBlockHandler(b); err != nil { return err } - syncedBlocks = append(syncedBlocks, b) + syncedBlocks = append(syncedBlocks, b.Block) return nil }, diff --git a/syncer/types.go b/syncer/types.go index 7f974720a1..3b4c6628d6 100644 --- a/syncer/types.go +++ b/syncer/types.go @@ -24,9 +24,11 @@ type Blockchain interface { // GetBlockByNumber returns block by number GetBlockByNumber(uint64, bool) (*types.Block, bool) // VerifyFinalizedBlock verifies finalized block - VerifyFinalizedBlock(*types.Block) error + VerifyFinalizedBlock(block *types.Block) (*types.FullBlock, error) // WriteBlock writes a given block to chain WriteBlock(*types.Block, string) error + // WriteFullBlock writes a given block to chain and saves its receipts to cache + WriteFullBlock(*types.FullBlock, string) error } type Network interface { @@ -63,7 +65,7 @@ type Syncer interface { // HasSyncPeer returns whether syncer has the peer syncer can sync with HasSyncPeer() bool // Sync starts routine to sync blocks - Sync(func(*types.Block) bool) error + Sync(func(*types.FullBlock) bool) error } type Progression interface { diff --git a/tests/testing.go b/tests/testing.go index f24d2c6755..83af02c043 100644 --- a/tests/testing.go +++ b/tests/testing.go @@ -440,6 +440,17 @@ var Forks = map[string]*chain.Forks{ Petersburg: chain.NewFork(0), Istanbul: chain.NewFork(0), }, + "London": { + Homestead: chain.NewFork(0), + EIP150: chain.NewFork(0), + EIP155: chain.NewFork(0), + EIP158: chain.NewFork(0), + Byzantium: chain.NewFork(0), + Constantinople: chain.NewFork(0), + Petersburg: chain.NewFork(0), + Istanbul: chain.NewFork(0), + London: chain.NewFork(0), + }, "FrontierToHomesteadAt5": { Homestead: chain.NewFork(5), }, diff --git a/tracker/event_tracker.go b/tracker/event_tracker.go new file mode 100644 index 0000000000..8857265957 --- /dev/null +++ b/tracker/event_tracker.go @@ -0,0 +1,97 @@ +package tracker + +import ( + "context" + + hcf "github.com/hashicorp/go-hclog" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/jsonrpc" + "github.com/umbracle/ethgo/tracker" + boltdbStore "github.com/umbracle/ethgo/tracker/store/boltdb" +) + +type eventSubscription interface { + AddLog(log *ethgo.Log) +} + +type EventTracker struct { + dbPath string + rpcEndpoint string + contractAddr ethgo.Address + subscriber eventSubscription + logger hcf.Logger +} + +func NewEventTracker( + dbPath string, + rpcEndpoint string, + contractAddr ethgo.Address, + subscriber eventSubscription, + logger hcf.Logger, +) *EventTracker { + return &EventTracker{ + dbPath: dbPath, + rpcEndpoint: rpcEndpoint, + contractAddr: contractAddr, + subscriber: subscriber, + logger: logger.Named("event_tracker"), + } +} + +func (e *EventTracker) Start(ctx context.Context) error { + provider, err := jsonrpc.NewClient(e.rpcEndpoint) + if err != nil { + return err + } + + store, err := boltdbStore.New(e.dbPath) + + if err != nil { + return err + } + + e.logger.Info("Start tracking events", "contract address", e.contractAddr) + + tt, err := tracker.NewTracker(provider.Eth(), + tracker.WithBatchSize(10), + tracker.WithStore(store), + tracker.WithFilter(&tracker.FilterConfig{ + Async: false, + Address: []ethgo.Address{ + e.contractAddr, + }, + }), + ) + if err != nil { + return err + } + + go func() { + if err := tt.Sync(ctx); err != nil { + e.logger.Error("failed to sync", "error", err) + } + }() + + go func() { + for { + select { + case <-ctx.Done(): + return + + case evnt := <-tt.EventCh: + if len(evnt.Removed) != 0 { + panic("this will not happen anymore after tracker v2") + } + + for _, log := range evnt.Added { + e.subscriber.AddLog(log) + } + + case <-tt.DoneCh: + e.logger.Info("historical sync done") + } + } + }() + + return nil +} diff --git a/tracker/event_tracker_test.go b/tracker/event_tracker_test.go new file mode 100644 index 0000000000..e60209f197 --- /dev/null +++ b/tracker/event_tracker_test.go @@ -0,0 +1,95 @@ +package tracker + +import ( + "context" + "os" + "path" + "sync" + "testing" + "time" + + "github.com/0xPolygon/polygon-edge/types" + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/require" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/testutil" +) + +type mockEventSubscriber struct { + lock sync.RWMutex + logs []*ethgo.Log +} + +func (m *mockEventSubscriber) AddLog(log *ethgo.Log) { + m.lock.Lock() + defer m.lock.Unlock() + + if len(m.logs) == 0 { + m.logs = []*ethgo.Log{} + } + + m.logs = append(m.logs, log) +} + +func (m *mockEventSubscriber) len() int { + m.lock.RLock() + defer m.lock.RUnlock() + + return len(m.logs) +} + +func TestEventTracker_TrackSyncEvents(t *testing.T) { + t.Parallel() + + server := testutil.DeployTestServer(t, nil) + + tmpDir, err := os.MkdirTemp("/tmp", "test-event-tracker") + defer os.RemoveAll(tmpDir) + require.NoError(t, err) + + cc := &testutil.Contract{} + cc.AddCallback(func() string { + return ` + event StateSync(uint256 indexed id, address indexed target, bytes data); + + function emitEvent() public payable { + emit StateSync(1, msg.sender, bytes("")); + } + ` + }) + + _, addr, err := server.DeployContract(cc) + require.NoError(t, err) + + // prefill with 10 events + for i := 0; i < 10; i++ { + receipt, err := server.TxnTo(addr, "emitEvent") + require.NoError(t, err) + require.Equal(t, uint64(types.ReceiptSuccess), receipt.Status) + } + + sub := &mockEventSubscriber{} + + tracker := &EventTracker{ + logger: hclog.NewNullLogger(), + subscriber: sub, + dbPath: path.Join(tmpDir, "test.db"), + rpcEndpoint: server.HTTPAddr(), + contractAddr: addr, + } + + err = tracker.Start(context.Background()) + require.NoError(t, err) + + time.Sleep(2 * time.Second) + require.Equal(t, sub.len(), 10) + // send 10 more events + for i := 0; i < 10; i++ { + receipt, err := server.TxnTo(addr, "emitEvent") + require.NoError(t, err) + require.Equal(t, uint64(types.ReceiptSuccess), receipt.Status) + } + + time.Sleep(2 * time.Second) + require.Equal(t, sub.len(), 20) +} diff --git a/txpool/proto/operator.pb.go b/txpool/proto/operator.pb.go index 242d437232..450fb9d006 100644 --- a/txpool/proto/operator.pb.go +++ b/txpool/proto/operator.pb.go @@ -1,8 +1,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 -// protoc v3.19.3 -// source: operator.proto +// protoc-gen-go v1.28.1 +// protoc v3.21.7 +// source: txpool/proto/operator.proto package proto @@ -74,11 +74,11 @@ func (x EventType) String() string { } func (EventType) Descriptor() protoreflect.EnumDescriptor { - return file_operator_proto_enumTypes[0].Descriptor() + return file_txpool_proto_operator_proto_enumTypes[0].Descriptor() } func (EventType) Type() protoreflect.EnumType { - return &file_operator_proto_enumTypes[0] + return &file_txpool_proto_operator_proto_enumTypes[0] } func (x EventType) Number() protoreflect.EnumNumber { @@ -87,7 +87,7 @@ func (x EventType) Number() protoreflect.EnumNumber { // Deprecated: Use EventType.Descriptor instead. func (EventType) EnumDescriptor() ([]byte, []int) { - return file_operator_proto_rawDescGZIP(), []int{0} + return file_txpool_proto_operator_proto_rawDescGZIP(), []int{0} } type AddTxnReq struct { @@ -102,7 +102,7 @@ type AddTxnReq struct { func (x *AddTxnReq) Reset() { *x = AddTxnReq{} if protoimpl.UnsafeEnabled { - mi := &file_operator_proto_msgTypes[0] + mi := &file_txpool_proto_operator_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -115,7 +115,7 @@ func (x *AddTxnReq) String() string { func (*AddTxnReq) ProtoMessage() {} func (x *AddTxnReq) ProtoReflect() protoreflect.Message { - mi := &file_operator_proto_msgTypes[0] + mi := &file_txpool_proto_operator_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -128,7 +128,7 @@ func (x *AddTxnReq) ProtoReflect() protoreflect.Message { // Deprecated: Use AddTxnReq.ProtoReflect.Descriptor instead. func (*AddTxnReq) Descriptor() ([]byte, []int) { - return file_operator_proto_rawDescGZIP(), []int{0} + return file_txpool_proto_operator_proto_rawDescGZIP(), []int{0} } func (x *AddTxnReq) GetRaw() *anypb.Any { @@ -156,7 +156,7 @@ type AddTxnResp struct { func (x *AddTxnResp) Reset() { *x = AddTxnResp{} if protoimpl.UnsafeEnabled { - mi := &file_operator_proto_msgTypes[1] + mi := &file_txpool_proto_operator_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -169,7 +169,7 @@ func (x *AddTxnResp) String() string { func (*AddTxnResp) ProtoMessage() {} func (x *AddTxnResp) ProtoReflect() protoreflect.Message { - mi := &file_operator_proto_msgTypes[1] + mi := &file_txpool_proto_operator_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -182,7 +182,7 @@ func (x *AddTxnResp) ProtoReflect() protoreflect.Message { // Deprecated: Use AddTxnResp.ProtoReflect.Descriptor instead. func (*AddTxnResp) Descriptor() ([]byte, []int) { - return file_operator_proto_rawDescGZIP(), []int{1} + return file_txpool_proto_operator_proto_rawDescGZIP(), []int{1} } func (x *AddTxnResp) GetTxHash() string { @@ -203,7 +203,7 @@ type TxnPoolStatusResp struct { func (x *TxnPoolStatusResp) Reset() { *x = TxnPoolStatusResp{} if protoimpl.UnsafeEnabled { - mi := &file_operator_proto_msgTypes[2] + mi := &file_txpool_proto_operator_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -216,7 +216,7 @@ func (x *TxnPoolStatusResp) String() string { func (*TxnPoolStatusResp) ProtoMessage() {} func (x *TxnPoolStatusResp) ProtoReflect() protoreflect.Message { - mi := &file_operator_proto_msgTypes[2] + mi := &file_txpool_proto_operator_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -229,7 +229,7 @@ func (x *TxnPoolStatusResp) ProtoReflect() protoreflect.Message { // Deprecated: Use TxnPoolStatusResp.ProtoReflect.Descriptor instead. func (*TxnPoolStatusResp) Descriptor() ([]byte, []int) { - return file_operator_proto_rawDescGZIP(), []int{2} + return file_txpool_proto_operator_proto_rawDescGZIP(), []int{2} } func (x *TxnPoolStatusResp) GetLength() uint64 { @@ -251,7 +251,7 @@ type SubscribeRequest struct { func (x *SubscribeRequest) Reset() { *x = SubscribeRequest{} if protoimpl.UnsafeEnabled { - mi := &file_operator_proto_msgTypes[3] + mi := &file_txpool_proto_operator_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -264,7 +264,7 @@ func (x *SubscribeRequest) String() string { func (*SubscribeRequest) ProtoMessage() {} func (x *SubscribeRequest) ProtoReflect() protoreflect.Message { - mi := &file_operator_proto_msgTypes[3] + mi := &file_txpool_proto_operator_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -277,7 +277,7 @@ func (x *SubscribeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubscribeRequest.ProtoReflect.Descriptor instead. func (*SubscribeRequest) Descriptor() ([]byte, []int) { - return file_operator_proto_rawDescGZIP(), []int{3} + return file_txpool_proto_operator_proto_rawDescGZIP(), []int{3} } func (x *SubscribeRequest) GetTypes() []EventType { @@ -299,7 +299,7 @@ type TxPoolEvent struct { func (x *TxPoolEvent) Reset() { *x = TxPoolEvent{} if protoimpl.UnsafeEnabled { - mi := &file_operator_proto_msgTypes[4] + mi := &file_txpool_proto_operator_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -312,7 +312,7 @@ func (x *TxPoolEvent) String() string { func (*TxPoolEvent) ProtoMessage() {} func (x *TxPoolEvent) ProtoReflect() protoreflect.Message { - mi := &file_operator_proto_msgTypes[4] + mi := &file_txpool_proto_operator_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -325,7 +325,7 @@ func (x *TxPoolEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use TxPoolEvent.ProtoReflect.Descriptor instead. func (*TxPoolEvent) Descriptor() ([]byte, []int) { - return file_operator_proto_rawDescGZIP(), []int{4} + return file_txpool_proto_operator_proto_rawDescGZIP(), []int{4} } func (x *TxPoolEvent) GetType() EventType { @@ -342,69 +342,70 @@ func (x *TxPoolEvent) GetTxHash() string { return "" } -var File_operator_proto protoreflect.FileDescriptor - -var file_operator_proto_rawDesc = []byte{ - 0x0a, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x12, 0x02, 0x76, 0x31, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, - 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x47, 0x0a, 0x09, - 0x41, 0x64, 0x64, 0x54, 0x78, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x26, 0x0a, 0x03, 0x72, 0x61, 0x77, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x03, 0x72, 0x61, - 0x77, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x24, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x54, 0x78, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x22, 0x2b, 0x0a, 0x11, 0x54, - 0x78, 0x6e, 0x50, 0x6f, 0x6f, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0x37, 0x0a, 0x10, 0x53, 0x75, 0x62, 0x73, - 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x05, - 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x76, 0x31, - 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x22, 0x48, 0x0a, 0x0b, 0x54, 0x78, 0x50, 0x6f, 0x6f, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x21, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, - 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x2a, 0x76, 0x0a, 0x09, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x44, 0x44, 0x45, - 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x4e, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, - 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x52, 0x4f, 0x4d, 0x4f, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, - 0x0b, 0x0a, 0x07, 0x44, 0x52, 0x4f, 0x50, 0x50, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, - 0x44, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x44, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x52, 0x55, - 0x4e, 0x45, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x4d, 0x4f, 0x54, 0x45, 0x44, 0x10, 0x05, 0x12, 0x13, - 0x0a, 0x0f, 0x50, 0x52, 0x55, 0x4e, 0x45, 0x44, 0x5f, 0x45, 0x4e, 0x51, 0x55, 0x45, 0x55, 0x45, - 0x44, 0x10, 0x06, 0x32, 0xa9, 0x01, 0x0a, 0x0f, 0x54, 0x78, 0x6e, 0x50, 0x6f, 0x6f, 0x6c, 0x4f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x37, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x54, - 0x78, 0x6e, 0x50, 0x6f, 0x6f, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x12, 0x27, 0x0a, 0x06, 0x41, 0x64, 0x64, 0x54, 0x78, 0x6e, 0x12, 0x0d, 0x2e, 0x76, 0x31, 0x2e, - 0x41, 0x64, 0x64, 0x54, 0x78, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x0e, 0x2e, 0x76, 0x31, 0x2e, 0x41, - 0x64, 0x64, 0x54, 0x78, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x34, 0x0a, 0x09, 0x53, 0x75, 0x62, - 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x14, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x73, - 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x76, - 0x31, 0x2e, 0x54, 0x78, 0x50, 0x6f, 0x6f, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x42, - 0x0f, 0x5a, 0x0d, 0x2f, 0x74, 0x78, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +var File_txpool_proto_operator_proto protoreflect.FileDescriptor + +var file_txpool_proto_operator_proto_rawDesc = []byte{ + 0x0a, 0x1b, 0x74, 0x78, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x76, + 0x31, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, + 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x47, 0x0a, 0x09, 0x41, 0x64, 0x64, + 0x54, 0x78, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x26, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, 0x12, + 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, + 0x6f, 0x6d, 0x22, 0x24, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x54, 0x78, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x12, 0x16, 0x0a, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x22, 0x2b, 0x0a, 0x11, 0x54, 0x78, 0x6e, 0x50, + 0x6f, 0x6f, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x16, 0x0a, + 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6c, + 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0x37, 0x0a, 0x10, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x05, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x22, 0x48, + 0x0a, 0x0b, 0x54, 0x78, 0x50, 0x6f, 0x6f, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x76, 0x31, + 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x2a, 0x76, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x44, 0x44, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x4e, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, + 0x0a, 0x08, 0x50, 0x52, 0x4f, 0x4d, 0x4f, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, + 0x44, 0x52, 0x4f, 0x50, 0x50, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x4d, + 0x4f, 0x54, 0x45, 0x44, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x52, 0x55, 0x4e, 0x45, 0x44, + 0x5f, 0x50, 0x52, 0x4f, 0x4d, 0x4f, 0x54, 0x45, 0x44, 0x10, 0x05, 0x12, 0x13, 0x0a, 0x0f, 0x50, + 0x52, 0x55, 0x4e, 0x45, 0x44, 0x5f, 0x45, 0x4e, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x06, + 0x32, 0xa9, 0x01, 0x0a, 0x0f, 0x54, 0x78, 0x6e, 0x50, 0x6f, 0x6f, 0x6c, 0x4f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x12, 0x37, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x78, 0x6e, 0x50, + 0x6f, 0x6f, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x27, 0x0a, + 0x06, 0x41, 0x64, 0x64, 0x54, 0x78, 0x6e, 0x12, 0x0d, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, + 0x54, 0x78, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x0e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x54, + 0x78, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x34, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, + 0x69, 0x62, 0x65, 0x12, 0x14, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x78, 0x50, 0x6f, 0x6f, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x42, 0x0f, 0x5a, 0x0d, + 0x2f, 0x74, 0x78, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( - file_operator_proto_rawDescOnce sync.Once - file_operator_proto_rawDescData = file_operator_proto_rawDesc + file_txpool_proto_operator_proto_rawDescOnce sync.Once + file_txpool_proto_operator_proto_rawDescData = file_txpool_proto_operator_proto_rawDesc ) -func file_operator_proto_rawDescGZIP() []byte { - file_operator_proto_rawDescOnce.Do(func() { - file_operator_proto_rawDescData = protoimpl.X.CompressGZIP(file_operator_proto_rawDescData) +func file_txpool_proto_operator_proto_rawDescGZIP() []byte { + file_txpool_proto_operator_proto_rawDescOnce.Do(func() { + file_txpool_proto_operator_proto_rawDescData = protoimpl.X.CompressGZIP(file_txpool_proto_operator_proto_rawDescData) }) - return file_operator_proto_rawDescData + return file_txpool_proto_operator_proto_rawDescData } -var file_operator_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_operator_proto_msgTypes = make([]protoimpl.MessageInfo, 5) -var file_operator_proto_goTypes = []interface{}{ +var file_txpool_proto_operator_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_txpool_proto_operator_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_txpool_proto_operator_proto_goTypes = []interface{}{ (EventType)(0), // 0: v1.EventType (*AddTxnReq)(nil), // 1: v1.AddTxnReq (*AddTxnResp)(nil), // 2: v1.AddTxnResp @@ -414,7 +415,7 @@ var file_operator_proto_goTypes = []interface{}{ (*anypb.Any)(nil), // 6: google.protobuf.Any (*emptypb.Empty)(nil), // 7: google.protobuf.Empty } -var file_operator_proto_depIdxs = []int32{ +var file_txpool_proto_operator_proto_depIdxs = []int32{ 6, // 0: v1.AddTxnReq.raw:type_name -> google.protobuf.Any 0, // 1: v1.SubscribeRequest.types:type_name -> v1.EventType 0, // 2: v1.TxPoolEvent.type:type_name -> v1.EventType @@ -431,13 +432,13 @@ var file_operator_proto_depIdxs = []int32{ 0, // [0:3] is the sub-list for field type_name } -func init() { file_operator_proto_init() } -func file_operator_proto_init() { - if File_operator_proto != nil { +func init() { file_txpool_proto_operator_proto_init() } +func file_txpool_proto_operator_proto_init() { + if File_txpool_proto_operator_proto != nil { return } if !protoimpl.UnsafeEnabled { - file_operator_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_txpool_proto_operator_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddTxnReq); i { case 0: return &v.state @@ -449,7 +450,7 @@ func file_operator_proto_init() { return nil } } - file_operator_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_txpool_proto_operator_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddTxnResp); i { case 0: return &v.state @@ -461,7 +462,7 @@ func file_operator_proto_init() { return nil } } - file_operator_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_txpool_proto_operator_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TxnPoolStatusResp); i { case 0: return &v.state @@ -473,7 +474,7 @@ func file_operator_proto_init() { return nil } } - file_operator_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_txpool_proto_operator_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SubscribeRequest); i { case 0: return &v.state @@ -485,7 +486,7 @@ func file_operator_proto_init() { return nil } } - file_operator_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_txpool_proto_operator_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TxPoolEvent); i { case 0: return &v.state @@ -502,19 +503,19 @@ func file_operator_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_operator_proto_rawDesc, + RawDescriptor: file_txpool_proto_operator_proto_rawDesc, NumEnums: 1, NumMessages: 5, NumExtensions: 0, NumServices: 1, }, - GoTypes: file_operator_proto_goTypes, - DependencyIndexes: file_operator_proto_depIdxs, - EnumInfos: file_operator_proto_enumTypes, - MessageInfos: file_operator_proto_msgTypes, + GoTypes: file_txpool_proto_operator_proto_goTypes, + DependencyIndexes: file_txpool_proto_operator_proto_depIdxs, + EnumInfos: file_txpool_proto_operator_proto_enumTypes, + MessageInfos: file_txpool_proto_operator_proto_msgTypes, }.Build() - File_operator_proto = out.File - file_operator_proto_rawDesc = nil - file_operator_proto_goTypes = nil - file_operator_proto_depIdxs = nil + File_txpool_proto_operator_proto = out.File + file_txpool_proto_operator_proto_rawDesc = nil + file_txpool_proto_operator_proto_goTypes = nil + file_txpool_proto_operator_proto_depIdxs = nil } diff --git a/txpool/proto/operator_grpc.pb.go b/txpool/proto/operator_grpc.pb.go index 2e1f977623..51eb1543a1 100644 --- a/txpool/proto/operator_grpc.pb.go +++ b/txpool/proto/operator_grpc.pb.go @@ -1,4 +1,8 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.21.7 +// source: txpool/proto/operator.proto package proto @@ -204,5 +208,5 @@ var TxnPoolOperator_ServiceDesc = grpc.ServiceDesc{ ServerStreams: true, }, }, - Metadata: "operator.proto", + Metadata: "txpool/proto/operator.proto", } diff --git a/txpool/proto/v1.pb.go b/txpool/proto/v1.pb.go index 3c0eb4ee6a..d01f487f78 100644 --- a/txpool/proto/v1.pb.go +++ b/txpool/proto/v1.pb.go @@ -1,16 +1,15 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.25.0 -// protoc v3.12.0 +// protoc-gen-go v1.28.1 +// protoc v3.21.7 // source: txpool/proto/v1.proto package proto import ( - proto "github.com/golang/protobuf/proto" - any "github.com/golang/protobuf/ptypes/any" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" reflect "reflect" sync "sync" ) @@ -22,16 +21,12 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// This is a compile-time assertion that a sufficiently up-to-date version -// of the legacy proto package is being used. -const _ = proto.ProtoPackageIsVersion4 - type Txn struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Raw *any.Any `protobuf:"bytes,1,opt,name=raw,proto3" json:"raw,omitempty"` + Raw *anypb.Any `protobuf:"bytes,1,opt,name=raw,proto3" json:"raw,omitempty"` } func (x *Txn) Reset() { @@ -66,7 +61,7 @@ func (*Txn) Descriptor() ([]byte, []int) { return file_txpool_proto_v1_proto_rawDescGZIP(), []int{0} } -func (x *Txn) GetRaw() *any.Any { +func (x *Txn) GetRaw() *anypb.Any { if x != nil { return x.Raw } @@ -100,8 +95,8 @@ func file_txpool_proto_v1_proto_rawDescGZIP() []byte { var file_txpool_proto_v1_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_txpool_proto_v1_proto_goTypes = []interface{}{ - (*Txn)(nil), // 0: v1.Txn - (*any.Any)(nil), // 1: google.protobuf.Any + (*Txn)(nil), // 0: v1.Txn + (*anypb.Any)(nil), // 1: google.protobuf.Any } var file_txpool_proto_v1_proto_depIdxs = []int32{ 1, // 0: v1.Txn.raw:type_name -> google.protobuf.Any diff --git a/txpool/txpool.go b/txpool/txpool.go index 4d50857def..a0c6724cea 100644 --- a/txpool/txpool.go +++ b/txpool/txpool.go @@ -11,6 +11,7 @@ import ( "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/network" "github.com/0xPolygon/polygon-edge/state" + "github.com/0xPolygon/polygon-edge/state/runtime" "github.com/0xPolygon/polygon-edge/txpool/proto" "github.com/0xPolygon/polygon-edge/types" "github.com/armon/go-metrics" @@ -63,7 +64,6 @@ type txOrigin int const ( local txOrigin = iota // json-RPC/gRPC endpoints gossip // gossip protocol - reorg // legacy code ) func (o txOrigin) String() (s string) { @@ -72,8 +72,6 @@ func (o txOrigin) String() (s string) { s = "local" case gossip: s = "gossip" - case reorg: - s = "reorg" } return @@ -509,33 +507,16 @@ func (p *TxPool) Demote(tx *types.Transaction) { // ResetWithHeaders processes the transactions from the new // headers to sync the pool with the new state. func (p *TxPool) ResetWithHeaders(headers ...*types.Header) { - e := &blockchain.Event{ - NewChain: headers, - } - // process the txs in the event // to make sure the pool is up-to-date - p.processEvent(e) + p.processEvent(&blockchain.Event{ + NewChain: headers, + }) } // processEvent collects the latest nonces for each account containted // in the received event. Resets all known accounts with the new nonce. func (p *TxPool) processEvent(event *blockchain.Event) { - oldTxs := make(map[types.Hash]*types.Transaction) - - // Legacy reorg logic // - for _, header := range event.OldChain { - // transactions to be returned to the pool - block, ok := p.store.GetBlockByHash(header.Hash, true) - if !ok { - continue - } - - for _, tx := range block.Transactions { - oldTxs[tx.Hash] = tx - } - } - // Grab the latest state root now that the block has been inserted stateRoot := p.store.Header().StateRoot stateNonces := make(map[types.Address]uint64) @@ -578,17 +559,6 @@ func (p *TxPool) processEvent(event *blockchain.Event) { // update the result map stateNonces[addr] = latestNonce - - // Legacy reorg logic // - // Update the addTxns in case of reorgs - delete(oldTxs, tx.Hash) - } - } - - // Legacy reorg logic // - for _, tx := range oldTxs { - if err := p.addTx(reorg, tx); err != nil { - p.logger.Error("add tx", "err", err) } } @@ -635,8 +605,14 @@ func (p *TxPool) validateTx(tx *types.Transaction) error { } // Check if transaction can deploy smart contract - if tx.IsContractCreation() && !p.deploymentWhitelist.allowed(tx.From) { - return ErrSmartContractRestricted + if tx.IsContractCreation() { + if !p.deploymentWhitelist.allowed(tx.From) { + return ErrSmartContractRestricted + } + + if p.forks.EIP158 && len(tx.Input) > state.SpuriousDragonMaxCodeSize { + return runtime.ErrMaxCodeSizeExceeded + } } // Reject underpriced transactions diff --git a/txpool/txpool_test.go b/txpool/txpool_test.go index 7fae6ec82f..b5e8f64624 100644 --- a/txpool/txpool_test.go +++ b/txpool/txpool_test.go @@ -13,11 +13,14 @@ import ( "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/crypto" "github.com/0xPolygon/polygon-edge/helper/tests" + "github.com/0xPolygon/polygon-edge/state" + "github.com/0xPolygon/polygon-edge/state/runtime" "github.com/0xPolygon/polygon-edge/txpool/proto" "github.com/0xPolygon/polygon-edge/types" "github.com/golang/protobuf/ptypes/any" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const ( @@ -1712,6 +1715,44 @@ func TestPermissionSmartContractDeployment(t *testing.T) { ErrSmartContractRestricted, ) }) + + t.Run("Input larger then the MaxInitCodeSize", func(t *testing.T) { + t.Parallel() + pool := setupPool() + pool.forks.EIP158 = true + + input := make([]byte, state.SpuriousDragonMaxCodeSize+1) + _, err := rand.Read(input) + require.NoError(t, err) + + tx := newTx(defaultAddr, 0, 1) + tx.To = nil + tx.Input = input + + assert.ErrorIs(t, + pool.validateTx(signTx(tx)), + runtime.ErrMaxCodeSizeExceeded, + ) + }) + + t.Run("Input the same as MaxInitCodeSize", func(t *testing.T) { + t.Parallel() + pool := setupPool() + pool.forks.EIP158 = true + + input := make([]byte, state.SpuriousDragonMaxCodeSize) + _, err := rand.Read(input) + require.NoError(t, err) + + tx := newTx(defaultAddr, 0, 1) + tx.To = nil + tx.Input = input + + assert.NoError(t, + pool.validateTx(signTx(tx)), + runtime.ErrMaxCodeSizeExceeded, + ) + }) } /* "Integrated" tests */ diff --git a/txrelayer/txrelayer.go b/txrelayer/txrelayer.go new file mode 100644 index 0000000000..bc66d0cc6b --- /dev/null +++ b/txrelayer/txrelayer.go @@ -0,0 +1,189 @@ +package txrelayer + +import ( + "errors" + "fmt" + "sync" + "time" + + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/jsonrpc" + "github.com/umbracle/ethgo/wallet" +) + +const ( + defaultGasPrice = 1879048192 // 0x70000000 + defaultGasLimit = 5242880 // 0x500000 + DefaultRPCAddress = "http://127.0.0.1:8545" +) + +var ( + errNoAccounts = errors.New("no accounts registered") +) + +type TxRelayer interface { + // Call executes a message call immediately without creating a transaction on the blockchain + Call(from ethgo.Address, to ethgo.Address, input []byte) (string, error) + // SendTransaction signs given transaction by provided key and sends it to the blockchain + SendTransaction(txn *ethgo.Transaction, key ethgo.Key) (*ethgo.Receipt, error) + // SendTransactionLocal sends non-signed transaction + // (this function is meant only for testing purposes and is about to be removed at some point) + SendTransactionLocal(txn *ethgo.Transaction) (*ethgo.Receipt, error) +} + +var _ TxRelayer = (*TxRelayerImpl)(nil) + +type TxRelayerImpl struct { + ipAddress string + client *jsonrpc.Client + receiptTimeout time.Duration + + lock sync.Mutex +} + +func NewTxRelayer(opts ...TxRelayerOption) (TxRelayer, error) { + t := &TxRelayerImpl{ + ipAddress: "http://127.0.0.1:8545", + receiptTimeout: 50 * time.Millisecond, + } + for _, opt := range opts { + opt(t) + } + + if t.client == nil { + client, err := jsonrpc.NewClient(t.ipAddress) + if err != nil { + return nil, err + } + + t.client = client + } + + return t, nil +} + +// Call executes a message call immediately without creating a transaction on the blockchain +func (t *TxRelayerImpl) Call(from ethgo.Address, to ethgo.Address, input []byte) (string, error) { + callMsg := ðgo.CallMsg{ + From: from, + To: &to, + Data: input, + } + + return t.client.Eth().Call(callMsg, ethgo.Pending) +} + +// SendTransaction signs given transaction by provided key and sends it to the blockchain +func (t *TxRelayerImpl) SendTransaction(txn *ethgo.Transaction, key ethgo.Key) (*ethgo.Receipt, error) { + txnHash, err := t.sendTransactionLocked(txn, key) + if err != nil { + return nil, err + } + + return t.waitForReceipt(txnHash) +} + +func (t *TxRelayerImpl) sendTransactionLocked(txn *ethgo.Transaction, key ethgo.Key) (ethgo.Hash, error) { + t.lock.Lock() + defer t.lock.Unlock() + + nonce, err := t.client.Eth().GetNonce(key.Address(), ethgo.Pending) + if err != nil { + return ethgo.ZeroHash, err + } + + txn.Nonce = nonce + + if txn.GasPrice == 0 { + txn.GasPrice = defaultGasPrice + } + + if txn.Gas == 0 { + txn.Gas = defaultGasLimit + } + + chainID, err := t.client.Eth().ChainID() + if err != nil { + return ethgo.ZeroHash, err + } + + signer := wallet.NewEIP155Signer(chainID.Uint64()) + if txn, err = signer.SignTx(txn, key); err != nil { + return ethgo.ZeroHash, err + } + + data, err := txn.MarshalRLPTo(nil) + if err != nil { + return ethgo.ZeroHash, err + } + + return t.client.Eth().SendRawTransaction(data) +} + +// SendTransactionLocal sends non-signed transaction +// (this function is meant only for testing purposes and is about to be removed at some point) +func (t *TxRelayerImpl) SendTransactionLocal(txn *ethgo.Transaction) (*ethgo.Receipt, error) { + accounts, err := t.client.Eth().Accounts() + if err != nil { + return nil, err + } + + if len(accounts) == 0 { + return nil, errNoAccounts + } + + txn.From = accounts[0] + txn.Gas = defaultGasLimit + txn.GasPrice = defaultGasPrice + + txnHash, err := t.client.Eth().SendTransaction(txn) + if err != nil { + return nil, err + } + + return t.waitForReceipt(txnHash) +} + +func (t *TxRelayerImpl) waitForReceipt(hash ethgo.Hash) (*ethgo.Receipt, error) { + count := uint(0) + + for { + receipt, err := t.client.Eth().GetTransactionReceipt(hash) + if err != nil { + if err.Error() != "not found" { + return nil, err + } + } + + if receipt != nil { + return receipt, nil + } + + if count > 100 { + return nil, fmt.Errorf("timeout while waiting for transaction %s to be processed", hash) + } + + time.Sleep(t.receiptTimeout) + count++ + } +} + +type TxRelayerOption func(*TxRelayerImpl) + +func WithClient(client *jsonrpc.Client) TxRelayerOption { + return func(t *TxRelayerImpl) { + t.client = client + } +} + +func WithIPAddress(ipAddress string) TxRelayerOption { + return func(t *TxRelayerImpl) { + t.ipAddress = ipAddress + } +} + +func WithReceiptTimeout(receiptTimeout time.Duration) TxRelayerOption { + return func(t *TxRelayerImpl) { + t.receiptTimeout = receiptTimeout + } +} diff --git a/types/buildroot/buildroot.go b/types/buildroot/buildroot.go index 68431124f7..7b7b682c51 100644 --- a/types/buildroot/buildroot.go +++ b/types/buildroot/buildroot.go @@ -95,7 +95,7 @@ var numArenaPool fastrlp.ArenaPool func deriveSlow(num int, h func(indx int) []byte) []byte { t := itrie.NewTrie() - txn := t.Txn() + txn := t.Txn(nil) ar := numArenaPool.Get() for i := 0; i < num; i++ { diff --git a/types/encoding.go b/types/encoding.go index 436a61125b..3f3dce4330 100644 --- a/types/encoding.go +++ b/types/encoding.go @@ -39,7 +39,6 @@ func ParseUint256orHex(val *string) (*big.Int, error) { } b, ok := new(big.Int).SetString(str, base) - if !ok { return nil, fmt.Errorf("could not parse") } diff --git a/types/header.go b/types/header.go index ffc1eeff44..bf2750f05b 100644 --- a/types/header.go +++ b/types/header.go @@ -91,6 +91,11 @@ type Body struct { Uncles []*Header } +type FullBlock struct { + Block *Block + Receipts []*Receipt +} + type Block struct { Header *Header Transactions []*Transaction diff --git a/types/receipt.go b/types/receipt.go index 963cd740f5..8b15186257 100644 --- a/types/receipt.go +++ b/types/receipt.go @@ -28,6 +28,12 @@ type Receipt struct { GasUsed uint64 ContractAddress *Address TxHash Hash + + TransactionType TxType +} + +func (r *Receipt) IsLegacyTx() bool { + return r.TransactionType == LegacyTx } func (r *Receipt) SetStatus(s ReceiptStatus) { diff --git a/types/rlp_encoding_test.go b/types/rlp_encoding_test.go index 5af0c28251..34beaddc00 100644 --- a/types/rlp_encoding_test.go +++ b/types/rlp_encoding_test.go @@ -5,6 +5,8 @@ import ( "reflect" "testing" + "github.com/umbracle/fastrlp" + "github.com/stretchr/testify/assert" ) @@ -60,9 +62,7 @@ func TestRLPMarshall_And_Unmarshall_Transaction(t *testing.T) { unmarshalledTxn.ComputeHash() txn.Hash = unmarshalledTxn.Hash - if !reflect.DeepEqual(txn, unmarshalledTxn) { - t.Fatal("[ERROR] Unmarshalled transaction not equal to base transaction") - } + assert.Equal(t, txn, unmarshalledTxn, "[ERROR] Unmarshalled transaction not equal to base transaction") } func TestRLPStorage_Marshall_And_Unmarshall_Receipt(t *testing.T) { @@ -129,3 +129,74 @@ func TestRLPUnmarshal_Header_ComputeHash(t *testing.T) { assert.NoError(t, h2.UnmarshalRLP(data)) assert.Equal(t, h.Hash, h2.Hash) } + +func TestRLPMarshall_And_Unmarshall_TypedTransaction(t *testing.T) { + addrTo := StringToAddress("11") + addrFrom := StringToAddress("22") + originalTx := &Transaction{ + Nonce: 0, + GasPrice: big.NewInt(11), + Gas: 11, + To: &addrTo, + From: addrFrom, + Value: big.NewInt(1), + Input: []byte{1, 2}, + V: big.NewInt(25), + S: big.NewInt(26), + R: big.NewInt(27), + } + + txTypes := []TxType{ + StateTx, + LegacyTx, + } + + for _, v := range txTypes { + originalTx.Type = v + originalTx.ComputeHash() + + txRLP := originalTx.MarshalRLP() + + unmarshalledTx := new(Transaction) + assert.NoError(t, unmarshalledTx.UnmarshalRLP(txRLP)) + + unmarshalledTx.ComputeHash() + assert.Equal(t, originalTx.Type, unmarshalledTx.Type) + } +} + +func TestRLPMarshall_And_Unmarshall_TxType(t *testing.T) { + testTable := []struct { + name string + txType TxType + expectedErr bool + }{ + { + name: "StateTx", + txType: StateTx, + }, + { + name: "LegacyTx", + txType: LegacyTx, + }, + { + name: "undefined type", + txType: TxType(0x09), + expectedErr: true, + }, + } + + for _, tt := range testTable { + ar := &fastrlp.Arena{} + + var txType TxType + err := txType.unmarshalRLPFrom(nil, ar.NewBytes([]byte{byte(tt.txType)})) + + if tt.expectedErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.txType, txType) + } + } +} diff --git a/types/rlp_marshal.go b/types/rlp_marshal.go index 0b1a50ae59..7e244b9d27 100644 --- a/types/rlp_marshal.go +++ b/types/rlp_marshal.go @@ -4,6 +4,10 @@ import ( "github.com/umbracle/fastrlp" ) +const ( + RLPSingleByteUpperLimit = 0x7f +) + type RLPMarshaler interface { MarshalRLPTo(dst []byte) []byte } @@ -35,6 +39,10 @@ func (b *Block) MarshalRLPWith(ar *fastrlp.Arena) *fastrlp.Value { } else { v0 := ar.NewArray() for _, tx := range b.Transactions { + if tx.Type != LegacyTx { + v0.Set(ar.NewBytes([]byte{byte(tx.Type)})) + } + v0.Set(tx.MarshalRLPWith(ar)) } vv.Set(v0) @@ -92,7 +100,12 @@ func (r Receipts) MarshalRLPTo(dst []byte) []byte { func (r *Receipts) MarshalRLPWith(a *fastrlp.Arena) *fastrlp.Value { vv := a.NewArray() + for _, rr := range *r { + if !rr.IsLegacyTx() { + vv.Set(a.NewBytes([]byte{byte(rr.TransactionType)})) + } + vv.Set(rr.MarshalRLPWith(a)) } @@ -104,6 +117,10 @@ func (r *Receipt) MarshalRLP() []byte { } func (r *Receipt) MarshalRLPTo(dst []byte) []byte { + if !r.IsLegacyTx() { + dst = append(dst, byte(r.TransactionType)) + } + return MarshalRLPTo(r.MarshalRLPWith, dst) } @@ -160,6 +177,10 @@ func (t *Transaction) MarshalRLP() []byte { } func (t *Transaction) MarshalRLPTo(dst []byte) []byte { + if t.Type != LegacyTx { + dst = append(dst, byte(t.Type)) + } + return MarshalRLPTo(t.MarshalRLPWith, dst) } @@ -186,5 +207,9 @@ func (t *Transaction) MarshalRLPWith(arena *fastrlp.Arena) *fastrlp.Value { vv.Set(arena.NewBigInt(t.R)) vv.Set(arena.NewBigInt(t.S)) + if t.Type == StateTx { + vv.Set(arena.NewBytes((t.From).Bytes())) + } + return vv } diff --git a/types/rlp_marshal_storage.go b/types/rlp_marshal_storage.go index 4c3105980c..dcef5edaa3 100644 --- a/types/rlp_marshal_storage.go +++ b/types/rlp_marshal_storage.go @@ -9,17 +9,17 @@ type RLPStoreMarshaler interface { } func (b *Body) MarshalRLPTo(dst []byte) []byte { - return MarshalRLPTo(b.MarshalRLPWith, dst) + return MarshalRLPTo(b.marshalRLPWith, dst) } -func (b *Body) MarshalRLPWith(ar *fastrlp.Arena) *fastrlp.Value { +func (b *Body) marshalRLPWith(ar *fastrlp.Arena) *fastrlp.Value { vv := ar.NewArray() if len(b.Transactions) == 0 { vv.Set(ar.NewNullArray()) } else { v0 := ar.NewArray() for _, tx := range b.Transactions { - v0.Set(tx.MarshalStoreRLPWith(ar)) + v0.Set(tx.marshalStoreRLPWith(ar)) } vv.Set(v0) } @@ -38,11 +38,16 @@ func (b *Body) MarshalRLPWith(ar *fastrlp.Arena) *fastrlp.Value { } func (t *Transaction) MarshalStoreRLPTo(dst []byte) []byte { - return MarshalRLPTo(t.MarshalStoreRLPWith, dst) + return MarshalRLPTo(t.marshalStoreRLPWith, dst) } -func (t *Transaction) MarshalStoreRLPWith(a *fastrlp.Arena) *fastrlp.Value { +func (t *Transaction) marshalStoreRLPWith(a *fastrlp.Arena) *fastrlp.Value { vv := a.NewArray() + + if t.Type != LegacyTx { + vv.Set(a.NewBytes([]byte{byte(t.Type)})) + } + // consensus part vv.Set(t.MarshalRLPWith(a)) // context part @@ -52,25 +57,30 @@ func (t *Transaction) MarshalStoreRLPWith(a *fastrlp.Arena) *fastrlp.Value { } func (r Receipts) MarshalStoreRLPTo(dst []byte) []byte { - return MarshalRLPTo(r.MarshalStoreRLPWith, dst) + return MarshalRLPTo(r.marshalStoreRLPWith, dst) } -func (r *Receipts) MarshalStoreRLPWith(a *fastrlp.Arena) *fastrlp.Value { +func (r *Receipts) marshalStoreRLPWith(a *fastrlp.Arena) *fastrlp.Value { vv := a.NewArray() for _, rr := range *r { - vv.Set(rr.MarshalStoreRLPWith(a)) + vv.Set(rr.marshalStoreRLPWith(a)) } return vv } func (r *Receipt) MarshalStoreRLPTo(dst []byte) []byte { - return MarshalRLPTo(r.MarshalStoreRLPWith, dst) + return MarshalRLPTo(r.marshalStoreRLPWith, dst) } -func (r *Receipt) MarshalStoreRLPWith(a *fastrlp.Arena) *fastrlp.Value { +func (r *Receipt) marshalStoreRLPWith(a *fastrlp.Arena) *fastrlp.Value { // use the hash part vv := a.NewArray() + + if !r.IsLegacyTx() { + vv.Set(a.NewBytes([]byte{byte(r.TransactionType)})) + } + vv.Set(r.MarshalRLPWith(a)) if r.ContractAddress == nil { diff --git a/types/rlp_unmarshal.go b/types/rlp_unmarshal.go index dac99580cc..2e2ac0203c 100644 --- a/types/rlp_unmarshal.go +++ b/types/rlp_unmarshal.go @@ -13,6 +13,8 @@ type RLPUnmarshaler interface { type unmarshalRLPFunc func(p *fastrlp.Parser, v *fastrlp.Value) error +type unmarshalRLPFromFunc func(TxType, *fastrlp.Parser, *fastrlp.Value) error + func UnmarshalRlp(obj unmarshalRLPFunc, input []byte) error { pr := fastrlp.DefaultParserPool.Get() @@ -23,7 +25,7 @@ func UnmarshalRlp(obj unmarshalRLPFunc, input []byte) error { return err } - if err := obj(pr, v); err != nil { + if err = obj(pr, v); err != nil { fastrlp.DefaultParserPool.Put(pr) return err @@ -34,11 +36,58 @@ func UnmarshalRlp(obj unmarshalRLPFunc, input []byte) error { return nil } +func unmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value, cb unmarshalRLPFromFunc) error { + elems, err := v.GetElems() + if err != nil { + return err + } + + for i := 0; i < len(elems); i++ { + // Non-legacy tx raw contains a tx type prefix in the beginning according to EIP-2718. + // Here we check if the first element is a tx type and unmarshal it first. + txType := LegacyTx + if elems[i].Type() == fastrlp.TypeBytes { + if err = txType.unmarshalRLPFrom(p, elems[i]); err != nil { + return err + } + + // Then we increment element number in order to go to the actual tx data raw below. + i++ + } + + if err = cb(txType, p, elems[i]); err != nil { + return err + } + } + + return nil +} + +func (t *TxType) unmarshalRLPFrom(_ *fastrlp.Parser, v *fastrlp.Value) error { + bytes, err := v.Bytes() + if err != nil { + return err + } + + if l := len(bytes); l != 1 { + return fmt.Errorf("expected 1 byte transaction type, but size is %d", l) + } + + tt, err := txTypeFromByte(bytes[0]) + if err != nil { + return err + } + + *t = tt + + return nil +} + func (b *Block) UnmarshalRLP(input []byte) error { - return UnmarshalRlp(b.UnmarshalRLPFrom, input) + return UnmarshalRlp(b.unmarshalRLPFrom, input) } -func (b *Block) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { +func (b *Block) unmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { elems, err := v.GetElems() if err != nil { return err @@ -50,23 +99,27 @@ func (b *Block) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { // header b.Header = &Header{} - if err := b.Header.UnmarshalRLPFrom(p, elems[0]); err != nil { + if err = b.Header.unmarshalRLPFrom(p, elems[0]); err != nil { return err } // transactions - txns, err := elems[1].GetElems() - if err != nil { - return err - } + if err = unmarshalRLPFrom(p, elems[1], func(txType TxType, p *fastrlp.Parser, v *fastrlp.Value) error { + bTxn := &Transaction{ + Type: txType, + } - for _, txn := range txns { - bTxn := &Transaction{} - if err := bTxn.UnmarshalRLPFrom(p, txn); err != nil { + if err = bTxn.unmarshalRLPFrom(p, v); err != nil { return err } + bTxn = bTxn.ComputeHash() + b.Transactions = append(b.Transactions, bTxn) + + return nil + }); err != nil { + return err } // uncles @@ -77,7 +130,7 @@ func (b *Block) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { for _, uncle := range uncles { bUncle := &Header{} - if err := bUncle.UnmarshalRLPFrom(p, uncle); err != nil { + if err = bUncle.unmarshalRLPFrom(p, uncle); err != nil { return err } @@ -88,10 +141,10 @@ func (b *Block) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { } func (h *Header) UnmarshalRLP(input []byte) error { - return UnmarshalRlp(h.UnmarshalRLPFrom, input) + return UnmarshalRlp(h.unmarshalRLPFrom, input) } -func (h *Header) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { +func (h *Header) unmarshalRLPFrom(_ *fastrlp.Parser, v *fastrlp.Value) error { elems, err := v.GetElems() if err != nil { return err @@ -172,33 +225,43 @@ func (h *Header) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { } func (r *Receipts) UnmarshalRLP(input []byte) error { - return UnmarshalRlp(r.UnmarshalRLPFrom, input) + return UnmarshalRlp(r.unmarshalRLPFrom, input) } -func (r *Receipts) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { - elems, err := v.GetElems() - if err != nil { - return err - } +func (r *Receipts) unmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { + return unmarshalRLPFrom(p, v, func(txType TxType, p *fastrlp.Parser, v *fastrlp.Value) error { + obj := &Receipt{ + TransactionType: txType, + } - for _, elem := range elems { - rr := &Receipt{} - if err := rr.UnmarshalRLPFrom(p, elem); err != nil { + if err := obj.unmarshalRLPFrom(p, v); err != nil { return err } - (*r) = append(*r, rr) - } + *r = append(*r, obj) - return nil + return nil + }) } func (r *Receipt) UnmarshalRLP(input []byte) error { - return UnmarshalRlp(r.UnmarshalRLPFrom, input) + r.TransactionType = LegacyTx + offset := 0 + + if len(input) > 0 && input[0] <= RLPSingleByteUpperLimit { + var err error + if r.TransactionType, err = txTypeFromByte(input[0]); err != nil { + return err + } + + offset = 1 + } + + return UnmarshalRlp(r.unmarshalRLPFrom, input[offset:]) } -// UnmarshalRLP unmarshals a Receipt in RLP format -func (r *Receipt) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { +// unmarshalRLPFrom unmarshals a Receipt in RLP format +func (r *Receipt) unmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { elems, err := v.GetElems() if err != nil { return err @@ -229,6 +292,7 @@ func (r *Receipt) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { if r.CumulativeGasUsed, err = elems[1].GetUint64(); err != nil { return err } + // logsBloom if _, err = elems[2].GetBytes(r.LogsBloom[:0], 256); err != nil { return err @@ -242,7 +306,7 @@ func (r *Receipt) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { for _, elem := range logsElems { log := &Log{} - if err := log.UnmarshalRLPFrom(p, elem); err != nil { + if err = log.unmarshalRLPFrom(p, elem); err != nil { return err } @@ -252,7 +316,7 @@ func (r *Receipt) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { return nil } -func (l *Log) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { +func (l *Log) unmarshalRLPFrom(_ *fastrlp.Parser, v *fastrlp.Value) error { elems, err := v.GetElems() if err != nil { return err @@ -263,9 +327,10 @@ func (l *Log) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { } // address - if err := elems[0].GetAddr(l.Address[:]); err != nil { + if err = elems[0].GetAddr(l.Address[:]); err != nil { return err } + // topics topicElems, err := elems[1].GetElems() if err != nil { @@ -275,7 +340,7 @@ func (l *Log) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { l.Topics = make([]Hash, len(topicElems)) for indx, topic := range topicElems { - if err := topic.GetHash(l.Topics[indx][:]); err != nil { + if err = topic.GetHash(l.Topics[indx][:]); err != nil { return err } } @@ -289,11 +354,23 @@ func (l *Log) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { } func (t *Transaction) UnmarshalRLP(input []byte) error { - return UnmarshalRlp(t.UnmarshalRLPFrom, input) + t.Type = LegacyTx + offset := 0 + + if len(input) > 0 && input[0] <= RLPSingleByteUpperLimit { + var err error + if t.Type, err = txTypeFromByte(input[0]); err != nil { + return err + } + + offset = 1 + } + + return UnmarshalRlp(t.unmarshalRLPFrom, input[offset:]) } -// UnmarshalRLPFrom unmarshals a Transaction in RLP format -func (t *Transaction) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { +// unmarshalRLPFrom unmarshals a Transaction in RLP format +func (t *Transaction) unmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { elems, err := v.GetElems() if err != nil { return err @@ -309,15 +386,18 @@ func (t *Transaction) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) erro if t.Nonce, err = elems[0].GetUint64(); err != nil { return err } + // gasPrice t.GasPrice = new(big.Int) - if err := elems[1].GetBigInt(t.GasPrice); err != nil { + if err = elems[1].GetBigInt(t.GasPrice); err != nil { return err } + // gas if t.Gas, err = elems[2].GetUint64(); err != nil { return err } + // to if vv, _ := v.Get(3).Bytes(); len(vv) == 20 { // address @@ -327,11 +407,13 @@ func (t *Transaction) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) erro // reset To t.To = nil } + // value t.Value = new(big.Int) - if err := elems[4].GetBigInt(t.Value); err != nil { + if err = elems[4].GetBigInt(t.Value); err != nil { return err } + // input if t.Input, err = elems[5].GetBytes(t.Input[:0]); err != nil { return err @@ -348,11 +430,25 @@ func (t *Transaction) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) erro if err = elems[7].GetBigInt(t.R); err != nil { return err } + // S t.S = new(big.Int) if err = elems[8].GetBigInt(t.S); err != nil { return err } + if t.Type == StateTx { + // set From with default value + t.From = ZeroAddress + + // We need to set From field for state transaction, + // because we are using unique, predefined address, for sending such transactions + // From + if vv, err := v.Get(9).Bytes(); err == nil && len(vv) == AddressLength { + // address + t.From = BytesToAddress(vv) + } + } + return nil } diff --git a/types/rlp_unmarshal_storage.go b/types/rlp_unmarshal_storage.go index 3ab0368679..2e7524558d 100644 --- a/types/rlp_unmarshal_storage.go +++ b/types/rlp_unmarshal_storage.go @@ -1,6 +1,7 @@ package types import ( + "errors" "fmt" "github.com/umbracle/fastrlp" @@ -11,10 +12,10 @@ type RLPStoreUnmarshaler interface { } func (b *Body) UnmarshalRLP(input []byte) error { - return UnmarshalRlp(b.UnmarshalRLPFrom, input) + return UnmarshalRlp(b.unmarshalRLPFrom, input) } -func (b *Body) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { +func (b *Body) unmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { tuple, err := v.GetElems() if err != nil { return err @@ -25,18 +26,22 @@ func (b *Body) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { } // transactions - txns, err := tuple[0].GetElems() - if err != nil { - return err - } + if err = unmarshalRLPFrom(p, tuple[0], func(txType TxType, p *fastrlp.Parser, v *fastrlp.Value) error { + bTxn := &Transaction{ + Type: txType, + } - for _, txn := range txns { - bTxn := &Transaction{} - if err := bTxn.UnmarshalStoreRLPFrom(p, txn); err != nil { + if err = bTxn.unmarshalStoreRLPFrom(p, v); err != nil { return err } + bTxn = bTxn.ComputeHash() + b.Transactions = append(b.Transactions, bTxn) + + return nil + }); err != nil { + return err } // uncles @@ -47,7 +52,7 @@ func (b *Body) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { for _, uncle := range uncles { bUncle := &Header{} - if err := bUncle.UnmarshalRLPFrom(p, uncle); err != nil { + if err = bUncle.unmarshalRLPFrom(p, uncle); err != nil { return err } @@ -58,68 +63,105 @@ func (b *Body) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { } func (t *Transaction) UnmarshalStoreRLP(input []byte) error { - return UnmarshalRlp(t.UnmarshalStoreRLPFrom, input) + t.Type = LegacyTx + + if len(input) > 0 && input[0] <= RLPSingleByteUpperLimit { + var err error + if t.Type, err = txTypeFromByte(input[0]); err != nil { + return err + } + } + + return UnmarshalRlp(t.unmarshalStoreRLPFrom, input) } -func (t *Transaction) UnmarshalStoreRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { +func (t *Transaction) unmarshalStoreRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { elems, err := v.GetElems() if err != nil { return err } - if len(elems) < 2 { - return fmt.Errorf("incorrect number of elements to decode transaction, expected 2 but found %d", len(elems)) + // come TransactionType first if exist + if len(elems) != 2 && len(elems) != 3 { + return fmt.Errorf("incorrect number of elements, expected 2 or 3 but found %d", len(elems)) + } + + if len(elems) == 3 { + if err = t.Type.unmarshalRLPFrom(p, elems[0]); err != nil { + return err + } + + elems = elems[1:] } // consensus part - if err := t.UnmarshalRLPFrom(p, elems[0]); err != nil { + if err = t.unmarshalRLPFrom(p, elems[0]); err != nil { return err } + // context part if err = elems[1].GetAddr(t.From[:]); err != nil { return err } + t.ComputeHash() + return nil } func (r *Receipts) UnmarshalStoreRLP(input []byte) error { - return UnmarshalRlp(r.UnmarshalStoreRLPFrom, input) + return UnmarshalRlp(r.unmarshalStoreRLPFrom, input) } -func (r *Receipts) UnmarshalStoreRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { - elems, err := v.GetElems() - if err != nil { - return err - } +func (r *Receipts) unmarshalStoreRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { + return unmarshalRLPFrom(p, v, func(txType TxType, p *fastrlp.Parser, v *fastrlp.Value) error { + obj := &Receipt{ + TransactionType: txType, + } - for _, elem := range elems { - rr := &Receipt{} - if err := rr.UnmarshalStoreRLPFrom(p, elem); err != nil { + if err := obj.unmarshalStoreRLPFrom(p, v); err != nil { return err } - (*r) = append(*r, rr) - } + *r = append(*r, obj) - return nil + return nil + }) } func (r *Receipt) UnmarshalStoreRLP(input []byte) error { - return UnmarshalRlp(r.UnmarshalStoreRLPFrom, input) + r.TransactionType = LegacyTx + + if len(input) > 0 && input[0] <= RLPSingleByteUpperLimit { + var err error + if r.TransactionType, err = txTypeFromByte(input[0]); err != nil { + return err + } + } + + return UnmarshalRlp(r.unmarshalStoreRLPFrom, input) } -func (r *Receipt) UnmarshalStoreRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { +func (r *Receipt) unmarshalStoreRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error { elems, err := v.GetElems() if err != nil { return err } - if len(elems) < 3 { - return fmt.Errorf("incorrect number of elements to decode receipt, expected at least 3 but found %d", len(elems)) + if len(elems) < 4 { + return errors.New("expected at least 4 elements") + } + + // come TransactionType first if exist + if len(elems) == 5 { + if err = r.TransactionType.unmarshalRLPFrom(p, elems[0]); err != nil { + return err + } + + elems = elems[1:] } - if err := r.UnmarshalRLPFrom(p, elems[0]); err != nil { + if err = r.unmarshalRLPFrom(p, elems[0]); err != nil { return err } @@ -142,7 +184,7 @@ func (r *Receipt) UnmarshalStoreRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) err // tx hash // backwards compatibility, old receipts did not marshal a TxHash if len(elems) == 4 { - vv, err := elems[3].Bytes() + vv, err = elems[3].Bytes() if err != nil { return err } diff --git a/types/transaction.go b/types/transaction.go index 010b249e87..92d4ef5e2c 100644 --- a/types/transaction.go +++ b/types/transaction.go @@ -1,12 +1,44 @@ package types import ( + "fmt" "math/big" "sync/atomic" "github.com/0xPolygon/polygon-edge/helper/keccak" ) +type TxType byte + +const ( + LegacyTx TxType = 0x0 + StateTx TxType = 0x7f + + StateTransactionGasLimit = 1000000 // some arbitrary default gas limit for state transactions +) + +func txTypeFromByte(b byte) (TxType, error) { + tt := TxType(b) + + switch tt { + case LegacyTx, StateTx: + return tt, nil + default: + return tt, fmt.Errorf("unknown transaction type: %d", b) + } +} + +func (t TxType) String() (s string) { + switch t { + case LegacyTx: + return "LegacyTx" + case StateTx: + return "StateTx" + } + + return +} + type Transaction struct { Nonce uint64 GasPrice *big.Int @@ -20,6 +52,8 @@ type Transaction struct { Hash Hash From Address + Type TxType + // Cache size atomic.Value } diff --git a/types/types.go b/types/types.go index 232460d5e5..24c20c929e 100644 --- a/types/types.go +++ b/types/types.go @@ -86,11 +86,11 @@ func (a Address) Bytes() []byte { } func StringToHash(str string) Hash { - return BytesToHash(stringToBytes(str)) + return BytesToHash(StringToBytes(str)) } func StringToAddress(str string) Address { - return BytesToAddress(stringToBytes(str)) + return BytesToAddress(StringToBytes(str)) } func AddressToString(address Address) string { @@ -108,7 +108,7 @@ func BytesToAddress(b []byte) Address { return a } -func stringToBytes(str string) []byte { +func StringToBytes(str string) []byte { str = strings.TrimPrefix(str, "0x") if len(str)%2 == 1 { str = "0" + str @@ -121,14 +121,14 @@ func stringToBytes(str string) []byte { // UnmarshalText parses a hash in hex syntax. func (h *Hash) UnmarshalText(input []byte) error { - *h = BytesToHash(stringToBytes(string(input))) + *h = BytesToHash(StringToBytes(string(input))) return nil } // UnmarshalText parses an address in hex syntax. func (a *Address) UnmarshalText(input []byte) error { - buf := stringToBytes(string(input)) + buf := StringToBytes(string(input)) if len(buf) != AddressLength { return fmt.Errorf("incorrect length") } @@ -153,3 +153,8 @@ var ( // EmptyUncleHash is the root when there are no uncles EmptyUncleHash = StringToHash("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347") ) + +type Proof struct { + Data []Hash // the proof himself + Metadata map[string]interface{} +}