├── .drone.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── abi ├── ethereum │ ├── ethereum.go │ ├── gravity │ │ └── gravity.go │ └── nebula │ │ └── nebula.go ├── solana │ └── instructions │ │ ├── gravity.go │ │ ├── instructions.go │ │ ├── nebula.go │ │ ├── nebula_contract.go │ │ ├── nebula_contract_test.go │ │ ├── nebula_deserializer.go │ │ ├── nebula_serializer.go │ │ └── nebula_test.go ├── type.go └── waves │ ├── gravity.abi │ └── nebula.abi ├── cmd └── gravity │ ├── commands │ ├── common.go │ ├── ledger.go │ └── oracle.go │ └── main.go ├── common ├── account │ ├── account.go │ ├── chain.go │ ├── nebula.go │ └── pubkey.go ├── adaptors │ ├── avax.go │ ├── binance.go │ ├── ethereum.go │ ├── factory.go │ ├── factory_test.go │ ├── fantom.go │ ├── heco.go │ ├── interface.go │ ├── okex.go │ ├── polygon.go │ ├── solana.go │ ├── waves.go │ ├── waves_test.go │ └── xdai.go ├── gravity │ ├── client.go │ └── client_test.go ├── hashing │ └── hash.go ├── helpers │ ├── state.go │ └── waves.go ├── score │ ├── calculator.go │ ├── calculator_test.go │ └── trustgraph │ │ ├── trustgraph.go │ │ └── trustgraph_test.go ├── state │ ├── state.go │ └── state_test.go ├── storage │ ├── commit.go │ ├── consuls.go │ ├── nebula.go │ ├── oracles.go │ ├── result.go │ ├── reveal.go │ ├── round.go │ ├── scores.go │ ├── solana.go │ ├── storage.go │ ├── system.go │ └── vote.go └── transactions │ └── transactions.go ├── config ├── addrbook.json ├── config.go ├── genesis.go ├── ledger.go └── oracle.go ├── contracts ├── ethereum │ ├── Gravity │ │ ├── Gravity.sol │ │ └── Models.sol │ ├── Mock │ │ ├── SubMockBytes.sol │ │ ├── SubMockInt.sol │ │ └── SubMockString.sol │ ├── Nebula │ │ ├── NModels.sol │ │ ├── Nebula.sol │ │ └── TestNebula.sol │ ├── interfaces │ │ ├── ISubscriberBytes.sol │ │ ├── ISubscriberInt.sol │ │ ├── ISubscriberString.sol │ │ └── ISubscription.sol │ └── libs │ │ └── Queue.sol └── waves │ ├── gravity.ride │ ├── nebula.ride │ ├── subMockBytes.ride │ └── subMockInt.ride ├── docker ├── README.md ├── build.sh ├── deploy-oracle.sh ├── deploy.sh ├── docker-compose.yml ├── entrypoint-ledger.sh ├── entrypoint-oracle.sh ├── ledger.dockerfile ├── oracle.dockerfile └── run-oracle.sh ├── docs └── README-ru.md ├── go.mod ├── go.sum ├── hardhat.config.ts ├── ledger ├── app │ └── app.go ├── query │ ├── consuls.go │ ├── oracles.go │ ├── query.go │ └── round.go └── scheduler │ ├── event_processor.go │ ├── http_listener.go │ ├── process.go │ └── scheduler.go ├── oracle ├── extractor │ ├── client.go │ └── models.go └── node │ ├── exe.go │ ├── models.go │ ├── node.go │ ├── pulse.go │ └── utils.go ├── package.json ├── rpc ├── config.go └── http.go ├── test ├── Gravity.spec.ts ├── Nebula.spec.ts └── shared │ ├── expect.ts │ └── fixtures.ts ├── tests └── waves │ ├── helper.go │ └── mock.go └── yarn.lock /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | type: docker 3 | name: gravity-core 4 | 5 | trigger: 6 | branch: 7 | - master 8 | - add-drone 9 | 10 | pipeline: 11 | publish-ledger: 12 | image: plugins/docker 13 | dockerfile: docker/ledger.dockerfile 14 | context: . 15 | repo: gravityhuborg/gravity-ledger 16 | tags: [latest, master] 17 | secrets: [docker_username,docker_password] 18 | when: 19 | branch: 20 | - master 21 | 22 | publish-oracle: 23 | image: plugins/docker 24 | dockerfile: docker/oracle.dockerfile 25 | context: . 26 | repo: gravityhuborg/gravity-oracle 27 | tags: [latest,master] 28 | secrets: [docker_username,docker_password] 29 | when: 30 | branch: 31 | - master 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | data/ 17 | .idea/ 18 | db/ 19 | .data/ 20 | .db/ 21 | ledger/.data/* 22 | */data 23 | */db 24 | */.db 25 | */gravity-hub 26 | */gravity-hub.exe 27 | */.idea 28 | */build 29 | */*/build 30 | */gh-node 31 | oracle/config.*.json 32 | # Deploy vendor 33 | nebula-address.txt 34 | migration.txt 35 | 36 | cmd/gravity/gravity 37 | .vscode/ 38 | /artifacts/ 39 | /cache/ 40 | /node_modules/ 41 | /typechain/ 42 | /coverage.json 43 | /coverage/ 44 | *DS_Store 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Gravity Hub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: ethereum waves 2 | 3 | ethereum: 4 | @type "npm" 2> /dev/null || echo 'Please install node.js and npm' 5 | @type "solc" 2> /dev/null || echo 'Please install solc' 6 | @type "abigen" 2> /dev/null || echo 'Please install abigen' 7 | # Core 8 | # Gravity 9 | abigen --pkg=gravity --sol="./contracts/ethereum/Gravity/Gravity.sol" --out="./abi/ethereum/gravity/gravity.go" 10 | abigen --pkg=nebula --sol="./contracts/ethereum/Nebula/Nebula.sol" --out="./abi/ethereum/nebula/nebula.go" 11 | echo "Ethereum ABI for Nebula & Gravity contracts updated!" 12 | 13 | waves: 14 | @type "npm" 2> /dev/null || echo 'Please install node.js and npm' 15 | @type "surfboard" 2> /dev/null || echo 'Please install sorfboard' 16 | surfboard compile ./contracts/waves/gravity.ride > ./abi/waves/gravity.abi 17 | surfboard compile ./contracts/waves/nebula.ride > ./abi/waves/nebula.abi 18 | echo "Waves ABI for Nebula and Gravity contracts updated!" 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gravity Core 2 | 3 | ## Additional versions: 4 | [RU version](docs/README-ru.md) 5 | 6 | ## Init ledger 7 | 8 | gravity ledger --home={home} init --network=devnet 9 | 10 | "--home" is the home directory for Gravity 11 | "--network" is the network type to generate configs for 12 | 13 | To initialize configuration of the ledger for devnet, use this command: 14 | 15 | gravity ledger --home={home} init 16 | 17 | To initialize configuration of the ledger for a custom network, use this command: 18 | 19 | gravity ledger --home={home} init --network=custom 20 | 21 | If the directory contains no privKey.json, it will be generated automatically. 22 | 23 | Configuration files: 24 | genesis.json - the genesis block for the ledger 25 | 26 | The structure of the configuration file is partly described [here](https://docs.tendermint.com/master/tendermint-core/using-tendermint.html), but there are a couple of custom sections: 27 | 28 | "сonsulsCount": 5, #number of consuls 29 | "initScore": { 30 | "{pubKey of the validator}": {gravity score}, 31 | }, 32 | "oraclesAddressByValidator": {. # the public keys of validators in InitScore 33 | "{pubKey of the validator}": { 34 | "ethereum": "{pubKey ethereum}", 35 | "waves": "{pubKey waves}" 36 | } 37 | } 38 | 39 | config.json - configuration of the ledger node 40 | 41 | The structure of the configuration file is partly described [here](https://docs.tendermint.com/master/tendermint-core/configuration.html), but there are a couple of custom sections: 42 | 43 | "adapters": { 44 | "ethereum": { 45 | "nodeUrl": "", # ethereum node address 46 | "gravityContractAddress": "" # gravity contract ethereum address 47 | }, 48 | "waves": { 49 | "NodeUrl": "", # waves node address 50 | "ChainId": "S", # chainId of the waves node 51 | "GravityContractAddress": # gravity contract waves address 52 | } 53 | } 54 | 55 | key_state.json - the state of validator's key (tendermint) 56 | 57 | node_key.json - the private key of the ledger node (tendermint) 58 | 59 | In order for a validator to participate in consensus, it is necessary for others to grade its behavior (Gravity score). If the score of the validator is higher than 0, it is a full-fledged participant of consensus and it can qualify for a consul position. 60 | 61 | ## RPC 62 | There are two types of RPC in the Gravity ledger: 63 | * public, set up in config.json RPC 64 | * private, set up as a flag during launch. A standard value is 127.0.0.1:2500 65 | 66 | ## Start ledger 67 | 68 | gravity ledger --home={home} start --rpc="127.0.0.1:2500" --bootstrap="http://127.0.0.1:26657" 69 | 70 | home is the home directory for Gravity configuration files 71 | rpc - host for the private rpc 72 | bootstrap - public rpc bootstrap of the node that the validator will connect to 73 | 74 | To launch the node in devnet, use this command: 75 | 76 | gravity ledger —home={home} start 77 | 78 | To launch the first node in the network: 79 | 80 | gravity ledger —home={home} start --bootstrap="" 81 | 82 | To launch all other nodes 83 | 84 | gravity ledger —home={home} start --bootstrap={url of the public rpc of the first node in config.json} 85 | 86 | —rpc="127.0.0.1:2500" - private rpc 87 | —bootstrap="" - url of the bootstrap node to connect to 88 | 89 | If you deploy the network locally, you can't set up more than two nodes on a single address (a Tendermint limitation). 90 | 91 | Other information on node setup can be found [here](https://docs.tendermint.com/master/spec/p2p/#) 92 | 93 | ## Voting 94 | To add a new validator to the consensus, it is necessary to vote for its gravity score 95 | The Gravity ledger needs to contain at least three validators. 96 | 97 | To grade other node's behavior, send a request to the private RPC : 98 | 99 | Route: http://{priv rpc host}/vote 100 | 101 | { 102 | "votes": [ 103 | { 104 | PubKey: {public key of the validator}, 105 | Score: {your score from 0 to 100} 106 | }... 107 | ] 108 | } 109 | 110 | If the request does not contain a validator mentioned before, the grade will be changed to zero. 111 | 112 | ## Create Nebula 113 | To create a Nebula, send a request to the private RPC: 114 | 115 | Route: http://{priv rpc host}//setNebula 116 | 117 | { 118 | "NebulaId": "nebula address" 119 | "ChainType": "ethereum/waves" 120 | "MaxPulseCountInBlock": {the max amount of pulses in a block} 121 | "MinScore": {the minumal score to participate in a nebula} 122 | } 123 | 124 | ## Init oracle 125 | 126 | gravity oracle --home={home} init " 127 | 128 | After the execution of the above command, the {home} directory will have a folder "nebulae" with a {nebula address}.json file. 129 | For a more custom setup, this file can be edited manually. 130 | 131 | ## Start oracle 132 | 133 | gravity oracle --home={home} start 134 | -------------------------------------------------------------------------------- /abi/solana/instructions/gravity.go: -------------------------------------------------------------------------------- 1 | package instructions 2 | 3 | import "github.com/portto/solana-go-sdk/common" 4 | 5 | /* 6 | pub initializer_pubkey: Pubkey, 7 | 8 | pub bft: u8, 9 | pub consuls: Vec, 10 | pub last_round: u64, 11 | pub multisig_account: Pubkey, 12 | */ 13 | 14 | type GravityContract struct { 15 | InitializerPubkey common.PublicKey 16 | Bft uint8 17 | Consuls []common.PublicKey 18 | LastRound uint64 19 | MultisigAccount common.PublicKey 20 | } 21 | 22 | func NewGravityContract() GravityContract { 23 | c := GravityContract{} 24 | c.Consuls = []common.PublicKey{} 25 | return c 26 | } 27 | -------------------------------------------------------------------------------- /abi/solana/instructions/instructions.go: -------------------------------------------------------------------------------- 1 | package instructions 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | 7 | "github.com/portto/solana-go-sdk/common" 8 | "github.com/portto/solana-go-sdk/types" 9 | ) 10 | 11 | func UpdateConsulsInstruction(fromAccount, programData, targetProgramID, multisigId common.PublicKey, signers []common.PublicKey, Bft uint8, Round uint64, Consuls []common.PublicKey) types.Instruction { 12 | consuls := []byte{} 13 | for i := 0; i < int(Bft); i++ { 14 | // acc := types.NewAccount() 15 | // Consuls[i][1:] 16 | consuls = append(consuls, Consuls[i][:]...) 17 | } 18 | data, err := common.SerializeData(struct { 19 | Instruction uint8 20 | Bft uint8 21 | Round uint64 22 | Consuls []byte 23 | }{ 24 | Instruction: 1, 25 | Bft: Bft, 26 | Round: Round, 27 | Consuls: consuls, 28 | }) 29 | if err != nil { 30 | panic(err) 31 | } 32 | fmt.Println("--------- RAW INSTRUCTION DATA -----------") 33 | fmt.Printf("%s\n", hex.EncodeToString(data)) 34 | fmt.Println("------- END RAW INSTRUCTION DATA ---------") 35 | 36 | accounts := []types.AccountMeta{ 37 | {PubKey: fromAccount, IsSigner: true, IsWritable: true}, 38 | {PubKey: programData, IsSigner: false, IsWritable: true}, 39 | {PubKey: multisigId, IsSigner: false, IsWritable: true}, 40 | } 41 | for _, s := range signers { 42 | accounts = append(accounts, types.AccountMeta{PubKey: s, IsSigner: true, IsWritable: false}) 43 | } 44 | return types.Instruction{ 45 | Accounts: accounts, 46 | ProgramID: targetProgramID, 47 | Data: data, 48 | } 49 | } 50 | 51 | func NebulaUpdateOraclesInstruction(fromAccount, targetProgramID, nebulaDataAccount, multisigAccount common.PublicKey, signers []common.PublicKey, Round uint64, Oracles []common.PublicKey, Bft uint8) types.Instruction { 52 | /* 53 | UpdateOracles { 54 | new_oracles: Vec, 55 | new_round: PulseID, 56 | } 57 | */ 58 | oracles := []byte{} 59 | for i := 0; i < len(Oracles); i++ { 60 | oracles = append(oracles, Oracles[i][:]...) 61 | } 62 | data, err := common.SerializeData(struct { 63 | Instruction uint8 64 | Bft uint8 65 | NewOracles []byte 66 | Round uint64 67 | }{ 68 | Instruction: 1, 69 | Bft: Bft, 70 | NewOracles: oracles, 71 | Round: Round, 72 | }) 73 | if err != nil { 74 | panic(err) 75 | } 76 | fmt.Println("--------- RAW INSTRUCTION DATA -----------") 77 | fmt.Printf("%s\n", hex.EncodeToString(data)) 78 | fmt.Println("------- END RAW INSTRUCTION DATA ---------") 79 | 80 | accounts := []types.AccountMeta{ 81 | {PubKey: fromAccount, IsSigner: true, IsWritable: true}, 82 | {PubKey: nebulaDataAccount, IsSigner: false, IsWritable: true}, 83 | {PubKey: multisigAccount, IsSigner: false, IsWritable: true}, 84 | } 85 | for _, s := range signers { 86 | accounts = append(accounts, types.AccountMeta{PubKey: s, IsSigner: true, IsWritable: false}) 87 | } 88 | return types.Instruction{ 89 | Accounts: accounts, 90 | ProgramID: targetProgramID, 91 | Data: data, 92 | } 93 | } 94 | 95 | func NebulaAddPulseInstruction(fromAccount, targetProgramID, nebulaId, multisigId, nebulaState common.PublicKey, signers []common.PublicKey, PulseId uint64, hash []byte) types.Instruction { 96 | /* 97 | SendHashValue { 98 | data_hash: Vec, 99 | } 100 | */ 101 | newHash := [64]byte{} 102 | copy(newHash[:], hash) 103 | 104 | data, err := common.SerializeData(struct { 105 | Instruction uint8 106 | //PulseID uint64 107 | Hash []byte 108 | }{ 109 | Instruction: 2, 110 | //PulseID: PulseId, 111 | Hash: newHash[:], 112 | }) 113 | if err != nil { 114 | panic(err) 115 | } 116 | fmt.Println("--------- RAW INSTRUCTION DATA -----------") 117 | fmt.Printf("%s\n", hex.EncodeToString(data)) 118 | fmt.Println("------- END RAW INSTRUCTION DATA ---------") 119 | 120 | accounts := []types.AccountMeta{ 121 | {PubKey: fromAccount, IsSigner: true, IsWritable: true}, 122 | {PubKey: nebulaState, IsSigner: false, IsWritable: true}, 123 | {PubKey: multisigId, IsSigner: false, IsWritable: true}, 124 | } 125 | for _, s := range signers { 126 | accounts = append(accounts, types.AccountMeta{PubKey: s, IsSigner: true, IsWritable: false}) 127 | } 128 | return types.Instruction{ 129 | Accounts: accounts, 130 | ProgramID: targetProgramID, 131 | Data: data, 132 | } 133 | } 134 | 135 | func NebulaSendValueToSubsInstruction(fromAccount, 136 | targetProgramID, nebulaId, nebulaState, nebulaMultisig common.PublicKey, 137 | ibportProgramAccount, ibportDataAccount, tokenProgramAddress, recipient, ibPortPDA, recipientOwner, ibPortPDAtokenAccount common.PublicKey, 138 | DataType uint8, value []byte, PulseId uint64, SubscriptionID [16]byte) types.Instruction { 139 | /* 140 | SendValueToSubs { 141 | data_type: DataType, 142 | pulse_id: PulseID, 143 | subscription_id: SubscriptionID, 144 | value: Vec 145 | }, 146 | */ 147 | data, err := common.SerializeData(struct { 148 | Instruction uint8 149 | Value []byte 150 | DataType uint8 151 | PulseID uint64 152 | SubscriptionID [16]byte 153 | }{ 154 | Instruction: 3, 155 | Value: value, 156 | DataType: DataType, 157 | PulseID: PulseId, 158 | SubscriptionID: SubscriptionID, 159 | }) 160 | if err != nil { 161 | panic(err) 162 | } 163 | fmt.Println("--------- RAW INSTRUCTION DATA -----------") 164 | fmt.Printf("%s\n", hex.EncodeToString(data)) 165 | fmt.Println("------- END RAW INSTRUCTION DATA ---------") 166 | 167 | accounts := []types.AccountMeta{ 168 | {PubKey: fromAccount, IsSigner: true, IsWritable: true}, 169 | {PubKey: nebulaState, IsSigner: false, IsWritable: true}, 170 | {PubKey: nebulaMultisig, IsSigner: false, IsWritable: true}, 171 | 172 | {PubKey: common.TokenProgramID, IsWritable: false, IsSigner: false}, 173 | {PubKey: ibportProgramAccount, IsWritable: false, IsSigner: false}, 174 | {PubKey: ibportDataAccount, IsWritable: true, IsSigner: false}, 175 | {PubKey: tokenProgramAddress, IsWritable: true, IsSigner: false}, 176 | {PubKey: recipient, IsWritable: true, IsSigner: false}, 177 | {PubKey: ibPortPDA, IsWritable: false, IsSigner: false}, 178 | {PubKey: recipientOwner, IsWritable: true, IsSigner: false}, 179 | {PubKey: ibPortPDAtokenAccount, IsWritable: true, IsSigner: false}, 180 | } 181 | return types.Instruction{ 182 | Accounts: accounts, 183 | ProgramID: targetProgramID, 184 | Data: data, 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /abi/solana/instructions/nebula.go: -------------------------------------------------------------------------------- 1 | package instructions 2 | 3 | import ( 4 | "github.com/portto/solana-go-sdk/common" 5 | "github.com/portto/solana-go-sdk/types" 6 | ) 7 | 8 | // type Pulse struct { 9 | // DataHash []uint8 10 | // Height uint64 11 | // } 12 | // type SubscriptionID [16]uint8 13 | 14 | // // struct Pulse { 15 | // // pub data_hash: Vec, 16 | // // pub height: u128, 17 | // // } 18 | // /* 19 | // pub struct NebulaContract { 20 | // pub rounds_dict: HashMap, 21 | // subscriptions_queue: NebulaQueue, 22 | // pub oracles: Vec, 23 | 24 | // pub bft: u8, 25 | // pub multisig_account: Pubkey, 26 | // pub gravity_contract: Pubkey, 27 | // pub data_type: DataType, 28 | // pub last_round: PulseID, 29 | 30 | // // subscription_ids: Vec, 31 | // pub last_pulse_id: PulseID, 32 | 33 | // subscriptions_map: HashMap, 34 | // pulses_map: HashMap, 35 | // is_pulse_sent: HashMap>, 36 | 37 | // pub is_initialized: bool, 38 | // pub initializer_pubkey: Pubkey, 39 | // } 40 | // */ 41 | 42 | // // serde: deserialize,serialize 43 | // type NebulaContract struct { 44 | // RoundsDict map[uint64]bool 45 | // SubscriptionsQueue []SubscriptionID 46 | // Oracles [][32]byte 47 | // Bft uint8 48 | // MultisigAccount [32]byte 49 | // GravityContract [32]byte 50 | // DataType uint8 51 | // LastRound uint64 52 | // LastPulseID uint64 53 | // SubscriptionsMap map[SubscriptionID]Subscription 54 | // PulsesMap map[uint64]Pulse 55 | // IsPulseSent map[uint64]map[SubscriptionID]bool 56 | // IsInitialized bool 57 | // InitializerPubKey [32]byte 58 | // } 59 | 60 | // type Subscription struct { 61 | // Sender [32]byte 62 | // ContractAddress [32]byte 63 | // MinConfirmations uint8 64 | // Reward uint64 65 | // } 66 | 67 | func InitNebulaInstruction(fromAccount, gravityProgramData, programID, nebulaAccount common.PublicKey, Bft uint8, DataType uint8, Oracles []common.PublicKey) (*types.Instruction, error) { 68 | /* 69 | InitContract { 70 | nebula_data_type: DataType, 71 | gravity_contract_program_id: Pubkey, 72 | initial_oracles: Vec, 73 | oracles_bft: u8, 74 | }, 75 | */ 76 | data, err := common.SerializeData(struct { 77 | Instruction uint8 78 | DataType uint8 79 | GravityProgramData common.PublicKey 80 | InitialOracles []common.PublicKey 81 | Bft uint8 82 | }{ 83 | Instruction: 0, 84 | DataType: DataType, 85 | GravityProgramData: gravityProgramData, 86 | InitialOracles: Oracles, 87 | Bft: Bft, 88 | }) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | return &types.Instruction{ 94 | Accounts: []types.AccountMeta{ 95 | {PubKey: fromAccount, IsSigner: true, IsWritable: false}, 96 | {PubKey: nebulaAccount, IsSigner: false, IsWritable: true}, 97 | {PubKey: gravityProgramData, IsSigner: false, IsWritable: true}, 98 | }, 99 | ProgramID: programID, 100 | Data: data, 101 | }, nil 102 | } 103 | 104 | func UpdateOraclesInstruction(fromAccount, programID, nebulaAccount common.PublicKey, CurrentOracles, NewOracles []common.PublicKey, pulseID uint64) (*types.Instruction, error) { 105 | /*UpdateOracles { 106 | new_oracles: Vec, 107 | new_round: PulseID, 108 | }, 109 | */ 110 | data, err := common.SerializeData(struct { 111 | Instruction uint8 112 | Bft uint8 113 | NewOracles []common.PublicKey 114 | PulseID uint64 115 | }{ 116 | Instruction: 1, 117 | Bft: uint8(len(CurrentOracles)), 118 | NewOracles: NewOracles, 119 | PulseID: uint64(pulseID), 120 | }) 121 | if err != nil { 122 | return nil, err 123 | } 124 | 125 | accounts := []types.AccountMeta{ 126 | {PubKey: fromAccount, IsSigner: true, IsWritable: false}, 127 | {PubKey: nebulaAccount, IsSigner: false, IsWritable: true}, 128 | } 129 | for _, oracle := range CurrentOracles { 130 | accounts = append(accounts, types.AccountMeta{ 131 | PubKey: oracle, 132 | IsSigner: true, 133 | IsWritable: false, 134 | }) 135 | } 136 | return &types.Instruction{ 137 | Accounts: accounts, 138 | ProgramID: programID, 139 | Data: data, 140 | }, nil 141 | 142 | } 143 | 144 | func SendHashValueInstruction(fromAccount, programID, nebulaAccount common.PublicKey, currentOracles []common.PublicKey, dataHash [16]byte) (*types.Instruction, error) { 145 | /* 146 | SendHashValue { 147 | data_hash: UUID, 148 | }, 149 | */ 150 | data, err := common.SerializeData(struct { 151 | Instruction uint8 152 | DataHash [16]byte 153 | }{ 154 | Instruction: 2, 155 | DataHash: dataHash, 156 | }) 157 | if err != nil { 158 | return nil, err 159 | } 160 | 161 | accounts := []types.AccountMeta{ 162 | {PubKey: fromAccount, IsSigner: true, IsWritable: false}, 163 | {PubKey: nebulaAccount, IsSigner: false, IsWritable: true}, 164 | } 165 | for _, oracle := range currentOracles { 166 | accounts = append(accounts, types.AccountMeta{ 167 | PubKey: oracle, 168 | IsSigner: true, 169 | IsWritable: false, 170 | }) 171 | } 172 | return &types.Instruction{ 173 | Accounts: accounts, 174 | ProgramID: programID, 175 | Data: data, 176 | }, nil 177 | } 178 | 179 | func SendValueToSubsInstruction(fromAccount, programID, nebulaAccount common.PublicKey, currentOracles []common.PublicKey, pulseID uint64, subscriptionID [16]byte, dType DataType, Hash []byte) (*types.Instruction, error) { 180 | 181 | /* 182 | SendValueToSubs { 183 | data_type: DataType, 184 | pulse_id: PulseID, 185 | subscription_id: UUID, 186 | }, 187 | */ 188 | data, err := common.SerializeData(struct { 189 | Instruction uint8 190 | DataHash []byte 191 | DataType DataType 192 | PulseID uint64 193 | SubsriptionID [16]byte 194 | }{ 195 | Instruction: 3, 196 | DataHash: Hash, 197 | DataType: dType, 198 | PulseID: pulseID, 199 | SubsriptionID: subscriptionID, 200 | }) 201 | if err != nil { 202 | return nil, err 203 | } 204 | 205 | accounts := []types.AccountMeta{ 206 | {PubKey: fromAccount, IsSigner: true, IsWritable: false}, 207 | {PubKey: nebulaAccount, IsSigner: false, IsWritable: true}, 208 | } 209 | for _, oracle := range currentOracles { 210 | accounts = append(accounts, types.AccountMeta{ 211 | PubKey: oracle, 212 | IsSigner: false, 213 | IsWritable: false, 214 | }) 215 | } 216 | return &types.Instruction{ 217 | Accounts: accounts, 218 | ProgramID: programID, 219 | Data: data, 220 | }, nil 221 | 222 | } 223 | -------------------------------------------------------------------------------- /abi/solana/instructions/nebula_contract.go: -------------------------------------------------------------------------------- 1 | package instructions 2 | 3 | import "github.com/portto/solana-go-sdk/common" 4 | 5 | type DataType uint8 6 | type Subscription struct { 7 | Sender common.PublicKey 8 | ContractAddress common.PublicKey 9 | MinConfirmations uint8 10 | Reward uint64 11 | } 12 | type Pulse struct { 13 | DataHash []byte 14 | //Height uint64 15 | } 16 | 17 | /* 18 | pub rounds_dict: HashMap, 19 | subscriptions_queue: NebulaQueue, 20 | pub oracles: Vec, 21 | 22 | pub bft: u8, 23 | pub multisig_account: Pubkey, 24 | pub gravity_contract: Pubkey, 25 | pub data_type: DataType, 26 | pub last_round: PulseID, 27 | 28 | subscription_ids: Vec, 29 | pub last_pulse_id: PulseID, 30 | 31 | subscriptions_map: HashMap, 32 | pulses_map: HashMap, 33 | is_pulse_sent: HashMap, 34 | 35 | pub is_initialized: bool, 36 | pub initializer_pubkey: Pubkey, 37 | 38 | 39 | 40 | 41 | pub oracles: Vec, 42 | 43 | pub bft: u8, 44 | pub multisig_account: Pubkey, 45 | pub gravity_contract: Pubkey, 46 | pub data_type: DataType, 47 | pub last_round: PulseID, 48 | 49 | pub last_pulse_id: PulseID, 50 | 51 | subscriptions_map: RecordHandler, 52 | 53 | pulses_map: RecordHandler, 54 | 55 | pub is_state_initialized: bool, 56 | pub initializer_pubkey: Pubkey, 57 | */ 58 | type NebulaContract struct { 59 | Oracles []common.PublicKey 60 | Bft uint8 61 | MultisigAccount common.PublicKey 62 | GravityContract common.PublicKey 63 | DataType DataType 64 | LastRound uint64 65 | LastPulseId uint64 66 | SubscriptionsMap struct { 67 | K [][16]uint8 68 | V []Subscription 69 | } 70 | PulsesMap struct { 71 | K []Pulse 72 | V []uint64 73 | } 74 | IsInitialized byte 75 | InitializerPubkey common.PublicKey 76 | } 77 | 78 | func NewNebulaContract() NebulaContract { 79 | c := NebulaContract{} 80 | c.Oracles = make([]common.PublicKey, 0) 81 | return c 82 | } 83 | -------------------------------------------------------------------------------- /abi/solana/instructions/nebula_deserializer.go: -------------------------------------------------------------------------------- 1 | package instructions 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "math" 8 | "unicode/utf8" 9 | 10 | "github.com/novifinancial/serde-reflection/serde-generate/runtime/golang/serde" 11 | ) 12 | 13 | // `NebulaDeserializer` is a partial implementation of the `Deserializer` interface. 14 | // It is used as an embedded struct by the Bincode and BCS deserializers. 15 | type NebulaDeserializer struct { 16 | Buffer *bytes.Buffer 17 | Input []byte 18 | containerDepthBudget uint64 19 | } 20 | 21 | func NewNebulaDeserializer(input []byte, max_container_depth uint64) *NebulaDeserializer { 22 | return &NebulaDeserializer{ 23 | Buffer: bytes.NewBuffer(input), 24 | Input: input, 25 | containerDepthBudget: max_container_depth, 26 | } 27 | } 28 | 29 | func (d *NebulaDeserializer) IncreaseContainerDepth() error { 30 | if d.containerDepthBudget == 0 { 31 | return errors.New("exceeded maximum container depth") 32 | } 33 | d.containerDepthBudget -= 1 34 | return nil 35 | } 36 | 37 | func (d *NebulaDeserializer) DecreaseContainerDepth() { 38 | d.containerDepthBudget += 1 39 | } 40 | 41 | // `deserializeLen` to be provided by the extending struct. 42 | func (d *NebulaDeserializer) DeserializeBytes() ([]byte, error) { 43 | 44 | ret := make([]byte, d.containerDepthBudget) 45 | n, err := d.Buffer.Read(ret) 46 | // if err == nil && uint64(n) < len { 47 | // return nil, errors.New("input is too short") 48 | // } 49 | return ret[:n+1], err 50 | } 51 | 52 | // `deserializeLen` to be provided by the extending struct. 53 | func (d *NebulaDeserializer) DeserializeStr() (string, error) { 54 | bytes, err := d.DeserializeBytes() 55 | if err != nil { 56 | return "", err 57 | } 58 | if !utf8.Valid(bytes) { 59 | return "", errors.New("invalid UTF8 string") 60 | } 61 | return string(bytes), nil 62 | } 63 | 64 | func (d *NebulaDeserializer) DeserializeBool() (bool, error) { 65 | ret, err := d.Buffer.ReadByte() 66 | if err != nil { 67 | return false, err 68 | } 69 | switch ret { 70 | case 0: 71 | return false, nil 72 | case 1: 73 | return true, nil 74 | default: 75 | return false, fmt.Errorf("invalid bool byte: expected 0 / 1, but got %d", ret) 76 | } 77 | } 78 | 79 | func (d *NebulaDeserializer) DeserializeUnit() (struct{}, error) { 80 | return struct{}{}, nil 81 | } 82 | 83 | // DeserializeChar is unimplemented. 84 | func (d *NebulaDeserializer) DeserializeChar() (rune, error) { 85 | return 0, errors.New("unimplemented") 86 | } 87 | 88 | func (d *NebulaDeserializer) DeserializeU8() (uint8, error) { 89 | ret, err := d.Buffer.ReadByte() 90 | return uint8(ret), err 91 | } 92 | 93 | func (d *NebulaDeserializer) DeserializeU16() (uint16, error) { 94 | var ret uint16 95 | for i := 0; i < 8*2; i += 8 { 96 | b, err := d.Buffer.ReadByte() 97 | if err != nil { 98 | return 0, err 99 | } 100 | ret = ret | uint16(b)<> 8)) 78 | return nil 79 | } 80 | 81 | func (s *NebulaSerializer) SerializeU32(value uint32) error { 82 | s.Buffer.WriteByte(byte(value)) 83 | s.Buffer.WriteByte(byte(value >> 8)) 84 | s.Buffer.WriteByte(byte(value >> 16)) 85 | s.Buffer.WriteByte(byte(value >> 24)) 86 | return nil 87 | } 88 | 89 | func (s *NebulaSerializer) SerializeU64(value uint64) error { 90 | s.Buffer.WriteByte(byte(value)) 91 | s.Buffer.WriteByte(byte(value >> 8)) 92 | s.Buffer.WriteByte(byte(value >> 16)) 93 | s.Buffer.WriteByte(byte(value >> 24)) 94 | s.Buffer.WriteByte(byte(value >> 32)) 95 | s.Buffer.WriteByte(byte(value >> 40)) 96 | s.Buffer.WriteByte(byte(value >> 48)) 97 | s.Buffer.WriteByte(byte(value >> 56)) 98 | return nil 99 | } 100 | 101 | func (s *NebulaSerializer) SerializeU128(value serde.Uint128) error { 102 | s.SerializeU64(value.Low) 103 | s.SerializeU64(value.High) 104 | return nil 105 | } 106 | 107 | func (s *NebulaSerializer) SerializeI8(value int8) error { 108 | s.SerializeU8(uint8(value)) 109 | return nil 110 | } 111 | 112 | func (s *NebulaSerializer) SerializeI16(value int16) error { 113 | s.SerializeU16(uint16(value)) 114 | return nil 115 | } 116 | 117 | func (s *NebulaSerializer) SerializeI32(value int32) error { 118 | s.SerializeU32(uint32(value)) 119 | return nil 120 | } 121 | 122 | func (s *NebulaSerializer) SerializeI64(value int64) error { 123 | s.SerializeU64(uint64(value)) 124 | return nil 125 | } 126 | 127 | func (s *NebulaSerializer) SerializeI128(value serde.Int128) error { 128 | s.SerializeU64(value.Low) 129 | s.SerializeI64(value.High) 130 | return nil 131 | } 132 | 133 | func (s *NebulaSerializer) SerializeF32(value float32) error { 134 | n := math.Float32bits(value) 135 | s.Buffer.WriteByte(byte(n)) 136 | s.Buffer.WriteByte(byte(n >> 8)) 137 | s.Buffer.WriteByte(byte(n >> 16)) 138 | s.Buffer.WriteByte(byte(n >> 24)) 139 | return nil 140 | } 141 | 142 | func (s *NebulaSerializer) SerializeF64(value float64) error { 143 | n := math.Float64bits(value) 144 | s.Buffer.WriteByte(byte(n)) 145 | s.Buffer.WriteByte(byte(n >> 8)) 146 | s.Buffer.WriteByte(byte(n >> 16)) 147 | s.Buffer.WriteByte(byte(n >> 24)) 148 | s.Buffer.WriteByte(byte(n >> 32)) 149 | s.Buffer.WriteByte(byte(n >> 40)) 150 | s.Buffer.WriteByte(byte(n >> 48)) 151 | s.Buffer.WriteByte(byte(n >> 56)) 152 | return nil 153 | } 154 | 155 | func (s *NebulaSerializer) SerializeOptionTag(value bool) error { 156 | return s.SerializeBool(value) 157 | } 158 | 159 | func (s *NebulaSerializer) GetBufferOffset() uint64 { 160 | return uint64(s.Buffer.Len()) 161 | } 162 | 163 | func (s *NebulaSerializer) GetBytes() []byte { 164 | return s.Buffer.Bytes() 165 | } 166 | 167 | func (s *NebulaSerializer) SerializeLen(value uint64) error { 168 | return s.SerializeU64(value) 169 | } 170 | func (s *NebulaSerializer) SerializeVariantIndex(value uint32) error { 171 | return s.SerializeU32(value) 172 | } 173 | 174 | func (s *NebulaSerializer) SortMapEntries(offsets []uint64) { 175 | 176 | } 177 | -------------------------------------------------------------------------------- /abi/solana/instructions/nebula_test.go: -------------------------------------------------------------------------------- 1 | package instructions 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "log" 7 | "reflect" 8 | "testing" 9 | 10 | "github.com/near/borsh-go" 11 | "github.com/portto/solana-go-sdk/types" 12 | ) 13 | 14 | func TestSerdeSerializer(t *testing.T) { 15 | a := types.NewAccount() 16 | c := NewNebulaContract() 17 | // c.RoundsDict[123] = 1 18 | // c.RoundsDict[12] = 0 19 | // c.RoundsDict[14] = 1 20 | c.LastRound = 13 21 | c.MultisigAccount = a.PublicKey 22 | data, err := borsh.Serialize(c) 23 | 24 | if err != nil { 25 | log.Fatalf("msgpack SerializeToBytes: %v", err) 26 | } 27 | fmt.Print(hex.Dump(data)) 28 | 29 | b := NewNebulaContract() 30 | err = borsh.Deserialize(&b, data) 31 | if err != nil { 32 | t.Error(err) 33 | } 34 | if !reflect.DeepEqual(c, b) { 35 | t.Error(c, b) 36 | } 37 | } 38 | 39 | type A struct { 40 | X uint64 41 | Y string 42 | B map[int]byte 43 | Z string `borsh_skip:"true"` // will skip this field when serializing/deserializing 44 | } 45 | 46 | func TestBorshSerializer(t *testing.T) { 47 | x := A{ 48 | X: 3301, 49 | Y: "liber primus", 50 | B: make(map[int]byte), 51 | } 52 | x.B[3] = 1 53 | data, err := borsh.Serialize(x) 54 | log.Print(data) 55 | if err != nil { 56 | t.Error(err) 57 | } 58 | y := new(A) 59 | err = borsh.Deserialize(y, data) 60 | if err != nil { 61 | t.Error(err) 62 | } 63 | if !reflect.DeepEqual(x, *y) { 64 | t.Error(x, y) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /abi/type.go: -------------------------------------------------------------------------------- 1 | package abi 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | type ExtractorType uint8 9 | 10 | var ( 11 | ErrParseExtractorType = errors.New("invalid parse extractor type") 12 | ) 13 | 14 | const ( 15 | Int64Type ExtractorType = iota 16 | StringType 17 | BytesType 18 | ) 19 | 20 | func ParseExtractorType(extractorType string) (ExtractorType, error) { 21 | switch strings.ToLower(extractorType) { 22 | case "int64": 23 | return Int64Type, nil 24 | case "string": 25 | return StringType, nil 26 | case "bytes": 27 | return BytesType, nil 28 | default: 29 | return 0, ErrParseExtractorType 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /abi/waves/gravity.abi: -------------------------------------------------------------------------------- 1 | AAIDAAAAAAAAAAkIARIFCgMICAEAAAALAAAAAApNYXhDb25zdWxzAAAAAAAAAAAFAQAAAA5nZXROdW1iZXJCeUtleQAAAAEAAAADa2V5BAAAAAckbWF0Y2gwCQAEGgAAAAIFAAAABHRoaXMFAAAAA2tleQMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAADSW50BAAAAAF2BQAAAAckbWF0Y2gwBQAAAAF2AAAAAAAAAAAAAQAAAA5nZXRTdHJpbmdCeUtleQAAAAEAAAADa2V5BAAAAAckbWF0Y2gwCQAEHQAAAAIFAAAABHRoaXMFAAAAA2tleQMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAAGU3RyaW5nBAAAAAF2BQAAAAckbWF0Y2gwBQAAAAF2AgAAAAABAAAAD2dldEJvb2xlYW5CeUtleQAAAAEAAAADa2V5BAAAAAckbWF0Y2gwCQAEGwAAAAIFAAAABHRoaXMFAAAAA2tleQMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAAHQm9vbGVhbgQAAAABdgUAAAAHJG1hdGNoMAUAAAABdgcAAAAAEUJmdENvZWZmaWNpZW50S2V5AgAAAA9iZnRfY29lZmZpY2llbnQAAAAADExhc3RSb3VuZEtleQIAAAAKbGFzdF9yb3VuZAEAAAAKQ29uc3Vsc0tleQAAAAEAAAAFcm91bmQJAAEsAAAAAgIAAAAIY29uc3Vsc18JAAGkAAAAAQUAAAAFcm91bmQAAAAADmJmdENvZWZmaWNpZW50CQEAAAAOZ2V0TnVtYmVyQnlLZXkAAAABBQAAABFCZnRDb2VmZmljaWVudEtleQAAAAAJbGFzdFJvdW5kCQEAAAAOZ2V0TnVtYmVyQnlLZXkAAAABBQAAAAxMYXN0Um91bmRLZXkBAAAADmNvbnN1bHNCeVJvdW5kAAAAAQAAAAVyb3VuZAkABLUAAAACCQEAAAAOZ2V0U3RyaW5nQnlLZXkAAAABCQEAAAAKQ29uc3Vsc0tleQAAAAEFAAAABXJvdW5kAgAAAAEsAQAAAAx2YWxpZGF0ZVNpZ24AAAADAAAABGhhc2gAAAAEc2lnbgAAAAZvcmFjbGUDCQEAAAACIT0AAAACBQAAAARzaWduAgAAAANuaWwDCQAB9AAAAAMFAAAABGhhc2gJAAJZAAAAAQUAAAAEc2lnbgkAAlkAAAABBQAAAAZvcmFjbGUAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAWkBAAAADXVwZGF0ZUNvbnN1bHMAAAADAAAACm5ld0NvbnN1bHMAAAALc3RyaW5nU2lnbnMAAAAFcm91bmQEAAAAB2NvbnN1bHMJAQAAAA5jb25zdWxzQnlSb3VuZAAAAAEFAAAACWxhc3RSb3VuZAQAAAADbXNnCQABmwAAAAEJAAEsAAAAAgkAASwAAAACBQAAAApuZXdDb25zdWxzAgAAAAEsCQABpAAAAAEFAAAABXJvdW5kBAAAAAVzaWducwkABLUAAAACBQAAAAtzdHJpbmdTaWducwIAAAABLAQAAAAFY291bnQJAABkAAAAAgkAAGQAAAACCQAAZAAAAAIJAABkAAAAAgkBAAAADHZhbGlkYXRlU2lnbgAAAAMFAAAAA21zZwkAAZEAAAACBQAAAAVzaWducwAAAAAAAAAAAAkAAZEAAAACBQAAAAdjb25zdWxzAAAAAAAAAAAACQEAAAAMdmFsaWRhdGVTaWduAAAAAwUAAAADbXNnCQABkQAAAAIFAAAABXNpZ25zAAAAAAAAAAABCQABkQAAAAIFAAAAB2NvbnN1bHMAAAAAAAAAAAEJAQAAAAx2YWxpZGF0ZVNpZ24AAAADBQAAAANtc2cJAAGRAAAAAgUAAAAFc2lnbnMAAAAAAAAAAAIJAAGRAAAAAgUAAAAHY29uc3VscwAAAAAAAAAAAgkBAAAADHZhbGlkYXRlU2lnbgAAAAMFAAAAA21zZwkAAZEAAAACBQAAAAVzaWducwAAAAAAAAAAAwkAAZEAAAACBQAAAAdjb25zdWxzAAAAAAAAAAADCQEAAAAMdmFsaWRhdGVTaWduAAAAAwUAAAADbXNnCQABkQAAAAIFAAAABXNpZ25zAAAAAAAAAAAECQABkQAAAAIFAAAAB2NvbnN1bHMAAAAAAAAAAAQDCQAAZwAAAAIFAAAACWxhc3RSb3VuZAUAAAAFcm91bmQJAAACAAAAAQIAAAAVcm91bmQgbGVzcyBsYXN0IHJvdW5kAwkAAGYAAAACBQAAAA5iZnRDb2VmZmljaWVudAUAAAAFY291bnQJAAACAAAAAQIAAAARaW52YWxpZCBiZnQgY291bnQJAQAAAAhXcml0ZVNldAAAAAEJAARMAAAAAgkBAAAACURhdGFFbnRyeQAAAAIJAQAAAApDb25zdWxzS2V5AAAAAQUAAAAFcm91bmQFAAAACm5ld0NvbnN1bHMJAARMAAAAAgkBAAAACURhdGFFbnRyeQAAAAIFAAAADExhc3RSb3VuZEtleQUAAAAFcm91bmQFAAAAA25pbAAAAACdHM12 2 | -------------------------------------------------------------------------------- /abi/waves/nebula.abi: -------------------------------------------------------------------------------- 1 | AAIDAAAAAAAAAA8IARIECgICCBIFCgMICAEAAAAfAAAAAAVXQVZFUwIAAAAFV0FWRVMAAAAAB0ludFR5cGUAAAAAAAAAAAAAAAAAClN0cmluZ1R5cGUAAAAAAAAAAAEAAAAACUJ5dGVzVHlwZQAAAAAAAAAAAgEAAAAOZ2V0TnVtYmVyQnlLZXkAAAABAAAAA2tleQQAAAAHJG1hdGNoMAkABBoAAAACBQAAAAR0aGlzBQAAAANrZXkDCQAAAQAAAAIFAAAAByRtYXRjaDACAAAAA0ludAQAAAABdgUAAAAHJG1hdGNoMAUAAAABdgAAAAAAAAAAAAEAAAANZ2V0Qnl0ZXNCeUtleQAAAAEAAAADa2V5BAAAAAckbWF0Y2gwCQAEHAAAAAIFAAAABHRoaXMFAAAAA2tleQMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAAKQnl0ZVZlY3RvcgQAAAABdgUAAAAHJG1hdGNoMAUAAAABdgEAAAAD0AAAAQAAAA5nZXRTdHJpbmdCeUtleQAAAAEAAAADa2V5BAAAAAckbWF0Y2gwCQAEHQAAAAIFAAAABHRoaXMFAAAAA2tleQMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAAGU3RyaW5nBAAAAAF2BQAAAAckbWF0Y2gwBQAAAAF2AgAAAAABAAAAGGdldFN0cmluZ0J5QWRkcmVzc0FuZEtleQAAAAIAAAAHYWRkcmVzcwAAAANrZXkEAAAAByRtYXRjaDAJAAQdAAAAAgUAAAAHYWRkcmVzcwUAAAADa2V5AwkAAAEAAAACBQAAAAckbWF0Y2gwAgAAAAZTdHJpbmcEAAAAAXYFAAAAByRtYXRjaDAFAAAAAXYCAAAAAAEAAAAYZ2V0TnVtYmVyQnlBZGRyZXNzQW5kS2V5AAAAAgAAAAdhZGRyZXNzAAAAA2tleQQAAAAHJG1hdGNoMAkABBoAAAACBQAAAAdhZGRyZXNzBQAAAANrZXkDCQAAAQAAAAIFAAAAByRtYXRjaDACAAAAA0ludAQAAAABdgUAAAAHJG1hdGNoMAUAAAABdgAAAAAAAAAAAAAAAAAKT3JhY2xlc0tleQIAAAAHb3JhY2xlcwAAAAAUU3Vic2NyaWJlckFkZHJlc3NLZXkCAAAAEnN1YnNjcmliZXJfYWRkcmVzcwAAAAAHVHlwZUtleQIAAAAEdHlwZQAAAAASR3Jhdml0eUNvbnRyYWN0S2V5AgAAABBncmF2aXR5X2NvbnRyYWN0AAAAABFCZnRDb2VmZmljaWVudEtleQIAAAAPYmZ0X2NvZWZmaWNpZW50AAAAAA1MYXN0SGVpZ2h0S2V5AgAAAAtsYXN0X2hlaWdodAAAAAAMTGFzdFJvdW5kS2V5AgAAAApsYXN0X3JvdW5kAAAAAA5MYXN0UHVsc2VJZEtleQIAAAANbGFzdF9wdWxzZV9pZAEAAAAOZ2V0SGFzaERhdGFLZXkAAAABAAAAB3B1bHNlSWQJAAEsAAAAAgIAAAAKZGF0YV9oYXNoXwkAAaQAAAABBQAAAAdwdWxzZUlkAQAAABNnZXRIZWlnaHRCeVB1bHNlS2V5AAAAAQAAAAdwdWxzZUlkCQABLAAAAAICAAAAB2hlaWdodF8JAAGkAAAAAQUAAAAHcHVsc2VJZAEAAAAKQ29uc3Vsc0tleQAAAAEAAAAFcm91bmQJAAEsAAAAAgIAAAAIY29uc3Vsc18JAAGkAAAAAQUAAAAFcm91bmQAAAAAB29yYWNsZXMJAAS1AAAAAgkBAAAADmdldFN0cmluZ0J5S2V5AAAAAQUAAAAKT3JhY2xlc0tleQIAAAABLAAAAAAOYmZ0Q29lZmZpY2llbnQJAQAAAA5nZXROdW1iZXJCeUtleQAAAAEFAAAAEUJmdENvZWZmaWNpZW50S2V5AAAAAA9ncmFjaXR5Q29udHJhY3QJAQAAABxAZXh0clVzZXIoYWRkcmVzc0Zyb21TdHJpbmcpAAAAAQkBAAAADmdldFN0cmluZ0J5S2V5AAAAAQUAAAASR3Jhdml0eUNvbnRyYWN0S2V5AAAAABBsYXN0R3Jhdml0eVJvdW5kCQEAAAAYZ2V0TnVtYmVyQnlBZGRyZXNzQW5kS2V5AAAAAgUAAAAPZ3JhY2l0eUNvbnRyYWN0BQAAAAxMYXN0Um91bmRLZXkAAAAAB2NvbnN1bHMJAAS1AAAAAgkBAAAAGGdldFN0cmluZ0J5QWRkcmVzc0FuZEtleQAAAAIFAAAAD2dyYWNpdHlDb250cmFjdAkBAAAACkNvbnN1bHNLZXkAAAABBQAAABBsYXN0R3Jhdml0eVJvdW5kAgAAAAEsAAAAABFzdWJzY3JpYmVyQWRkcmVzcwkBAAAADmdldFN0cmluZ0J5S2V5AAAAAQUAAAAUU3Vic2NyaWJlckFkZHJlc3NLZXkAAAAABHR5cGUJAQAAAA5nZXROdW1iZXJCeUtleQAAAAEFAAAAB1R5cGVLZXkAAAAAC2xhc3RQdWxzZUlkCQEAAAAOZ2V0TnVtYmVyQnlLZXkAAAABBQAAAA5MYXN0UHVsc2VJZEtleQEAAAALZ2V0SGFzaERhdGEAAAABAAAAB3B1bHNlSWQJAQAAAA1nZXRCeXRlc0J5S2V5AAAAAQkBAAAADmdldEhhc2hEYXRhS2V5AAAAAQUAAAAHcHVsc2VJZAEAAAAQZ2V0SGVpZ2h0QnlQdWxzZQAAAAEAAAAHcHVsc2VJZAkBAAAADmdldE51bWJlckJ5S2V5AAAAAQkBAAAAE2dldEhlaWdodEJ5UHVsc2VLZXkAAAABBQAAAAdwdWxzZUlkAQAAAAx2YWxpZGF0ZVNpZ24AAAADAAAABGhhc2gAAAAEc2lnbgAAAAZvcmFjbGUDCQEAAAACIT0AAAACBQAAAARzaWduAgAAAANuaWwDCQAB9AAAAAMFAAAABGhhc2gJAAJZAAAAAQUAAAAEc2lnbgkAAlkAAAABBQAAAAZvcmFjbGUAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAWkBAAAADXNlbmRIYXNoVmFsdWUAAAACAAAABGhhc2gAAAAFc2lnbnMEAAAACHNpZ25MaXN0CQAEtQAAAAIFAAAABXNpZ25zAgAAAAEsBAAAAAVjb3VudAkAAGQAAAACCQAAZAAAAAIJAABkAAAAAgkAAGQAAAACCQEAAAAMdmFsaWRhdGVTaWduAAAAAwUAAAAEaGFzaAkAAZEAAAACBQAAAAhzaWduTGlzdAAAAAAAAAAAAAkAAZEAAAACBQAAAAdvcmFjbGVzAAAAAAAAAAAACQEAAAAMdmFsaWRhdGVTaWduAAAAAwUAAAAEaGFzaAkAAZEAAAACBQAAAAhzaWduTGlzdAAAAAAAAAAAAQkAAZEAAAACBQAAAAdvcmFjbGVzAAAAAAAAAAABCQEAAAAMdmFsaWRhdGVTaWduAAAAAwUAAAAEaGFzaAkAAZEAAAACBQAAAAhzaWduTGlzdAAAAAAAAAAAAgkAAZEAAAACBQAAAAdvcmFjbGVzAAAAAAAAAAACCQEAAAAMdmFsaWRhdGVTaWduAAAAAwUAAAAEaGFzaAkAAZEAAAACBQAAAAhzaWduTGlzdAAAAAAAAAAAAwkAAZEAAAACBQAAAAdvcmFjbGVzAAAAAAAAAAADCQEAAAAMdmFsaWRhdGVTaWduAAAAAwUAAAAEaGFzaAkAAZEAAAACBQAAAAhzaWduTGlzdAAAAAAAAAAABAkAAZEAAAACBQAAAAdvcmFjbGVzAAAAAAAAAAAEAwkAAGYAAAACBQAAAA5iZnRDb2VmZmljaWVudAUAAAAFY291bnQJAAACAAAAAQIAAAARaW52YWxpZCBiZnQgY291bnQEAAAADmN1cnJlbnRQdWxzZUlkCQAAZAAAAAIFAAAAC2xhc3RQdWxzZUlkAAAAAAAAAAABCQEAAAAIV3JpdGVTZXQAAAABCQAETAAAAAIJAQAAAAlEYXRhRW50cnkAAAACCQEAAAAOZ2V0SGFzaERhdGFLZXkAAAABBQAAAA5jdXJyZW50UHVsc2VJZAUAAAAEaGFzaAkABEwAAAACCQEAAAAJRGF0YUVudHJ5AAAAAgkBAAAAE2dldEhlaWdodEJ5UHVsc2VLZXkAAAABBQAAAA5jdXJyZW50UHVsc2VJZAUAAAAGaGVpZ2h0CQAETAAAAAIJAQAAAAlEYXRhRW50cnkAAAACBQAAAA1MYXN0SGVpZ2h0S2V5BQAAAAZoZWlnaHQJAARMAAAAAgkBAAAACURhdGFFbnRyeQAAAAIFAAAADkxhc3RQdWxzZUlkS2V5BQAAAA5jdXJyZW50UHVsc2VJZAUAAAADbmlsAAAAAWkBAAAADXVwZGF0ZU9yYWNsZXMAAAADAAAAEG5ld1NvcnRlZE9yYWNsZXMAAAALc3RyaW5nU2lnbnMAAAAFcm91bmQEAAAABXNpZ25zCQAEtQAAAAIFAAAAC3N0cmluZ1NpZ25zAgAAAAEsBAAAAAVjb3VudAkAAGQAAAACCQAAZAAAAAIJAABkAAAAAgkAAGQAAAACCQEAAAAMdmFsaWRhdGVTaWduAAAAAwkAAZsAAAABBQAAABBuZXdTb3J0ZWRPcmFjbGVzCQABkQAAAAIFAAAABXNpZ25zAAAAAAAAAAAACQABkQAAAAIFAAAAB2NvbnN1bHMAAAAAAAAAAAAJAQAAAAx2YWxpZGF0ZVNpZ24AAAADCQABmwAAAAEFAAAAEG5ld1NvcnRlZE9yYWNsZXMJAAGRAAAAAgUAAAAFc2lnbnMAAAAAAAAAAAEJAAGRAAAAAgUAAAAHY29uc3VscwAAAAAAAAAAAQkBAAAADHZhbGlkYXRlU2lnbgAAAAMJAAGbAAAAAQUAAAAQbmV3U29ydGVkT3JhY2xlcwkAAZEAAAACBQAAAAVzaWducwAAAAAAAAAAAgkAAZEAAAACBQAAAAdjb25zdWxzAAAAAAAAAAACCQEAAAAMdmFsaWRhdGVTaWduAAAAAwkAAZsAAAABBQAAABBuZXdTb3J0ZWRPcmFjbGVzCQABkQAAAAIFAAAABXNpZ25zAAAAAAAAAAADCQABkQAAAAIFAAAAB2NvbnN1bHMAAAAAAAAAAAMJAQAAAAx2YWxpZGF0ZVNpZ24AAAADCQABmwAAAAEFAAAAEG5ld1NvcnRlZE9yYWNsZXMJAAGRAAAAAgUAAAAFc2lnbnMAAAAAAAAAAAQJAAGRAAAAAgUAAAAHY29uc3VscwAAAAAAAAAABAMJAABmAAAAAgUAAAAOYmZ0Q29lZmZpY2llbnQFAAAABWNvdW50CQAAAgAAAAECAAAAEWludmFsaWQgYmZ0IGNvdW50CQEAAAAIV3JpdGVTZXQAAAABCQAETAAAAAIJAQAAAAlEYXRhRW50cnkAAAACBQAAAApPcmFjbGVzS2V5BQAAABBuZXdTb3J0ZWRPcmFjbGVzCQAETAAAAAIJAQAAAAlEYXRhRW50cnkAAAACCQABLAAAAAIJAAEsAAAAAgUAAAAMTGFzdFJvdW5kS2V5AgAAAAFfCQABpAAAAAEFAAAABXJvdW5kBQAAAAVyb3VuZAUAAAADbmlsAAAAAQAAAAFpAQAAAA5zZW5kVmFsdWVUb1N1YgAAAAAEAAAAByRtYXRjaDAFAAAAAWkDCQAAAQAAAAIFAAAAByRtYXRjaDACAAAAF0ludm9rZVNjcmlwdFRyYW5zYWN0aW9uBAAAAAhpbnZva2VUeAUAAAAHJG1hdGNoMAQAAAAGdkJ5dGVzAwkAAAAAAAACBQAAAAR0eXBlBQAAAAdJbnRUeXBlBAAAAAF2BAAAAAckbWF0Y2gxCQABkQAAAAIIBQAAAAhpbnZva2VUeAAAAARhcmdzAAAAAAAAAAAAAwkAAAEAAAACBQAAAAckbWF0Y2gxAgAAAANJbnQEAAAAAXYFAAAAByRtYXRjaDEFAAAAAXYJAAACAAAAAQIAAAASaW52YWxpZCB2YWx1ZSB0eXBlCQABmgAAAAEFAAAAAXYDCQAAAAAAAAIFAAAABHR5cGUFAAAAClN0cmluZ1R5cGUEAAAAAXYEAAAAByRtYXRjaDEJAAGRAAAAAggFAAAACGludm9rZVR4AAAABGFyZ3MAAAAAAAAAAAADCQAAAQAAAAIFAAAAByRtYXRjaDECAAAABlN0cmluZwQAAAABdgUAAAAHJG1hdGNoMQUAAAABdgkAAAIAAAABAgAAABJpbnZhbGlkIHZhbHVlIHR5cGUJAAGbAAAAAQUAAAABdgMJAAAAAAAAAgUAAAAEdHlwZQUAAAAJQnl0ZXNUeXBlBAAAAAF2BAAAAAckbWF0Y2gxCQABkQAAAAIIBQAAAAhpbnZva2VUeAAAAARhcmdzAAAAAAAAAAAAAwkAAAEAAAACBQAAAAckbWF0Y2gxAgAAAApCeXRlVmVjdG9yBAAAAAF2BQAAAAckbWF0Y2gxBQAAAAF2CQAAAgAAAAECAAAAEmludmFsaWQgdmFsdWUgdHlwZQUAAAABdgkAAAIAAAABAgAAABJpbnZhbGlkIHZhbHVlIHR5cGUEAAAACHZQdWxzZUlkBAAAAAckbWF0Y2gxCQABkQAAAAIIBQAAAAhpbnZva2VUeAAAAARhcmdzAAAAAAAAAAABAwkAAAEAAAACBQAAAAckbWF0Y2gxAgAAAANJbnQEAAAACHZQdWxzZUlkBQAAAAckbWF0Y2gxBQAAAAh2UHVsc2VJZAkAAAIAAAABAgAAABNpbnZhbGlkIGhlaWdodCB0eXBlAwkBAAAAAiE9AAAAAggFAAAACGludm9rZVR4AAAACGZ1bmN0aW9uAgAAAAphdHRhY2hEYXRhCQAAAgAAAAECAAAAFWludmFsaWQgZnVuY3Rpb24gbmFtZQMJAQAAAAIhPQAAAAIJAAGQAAAAAQgFAAAACGludm9rZVR4AAAABGFyZ3MAAAAAAAAAAAIJAAACAAAAAQIAAAARaW52YWxpZCBhcmdzIHNpemUDCQEAAAACIT0AAAACCAUAAAAIaW52b2tlVHgAAAAEZEFwcAkBAAAAHEBleHRyVXNlcihhZGRyZXNzRnJvbVN0cmluZykAAAABBQAAABFzdWJzY3JpYmVyQWRkcmVzcwkAAAIAAAABAgAAABRpbnZhbGlkIGRhcHAgYWRkcmVzcwMJAQAAAAIhPQAAAAIJAQAAABBnZXRIZWlnaHRCeVB1bHNlAAAAAQUAAAAIdlB1bHNlSWQFAAAABmhlaWdodAkAAAIAAAABAgAAAA5pbnZhbGlkIGhlaWdodAMJAAAAAAAAAgkBAAAAC2dldEhhc2hEYXRhAAAAAQUAAAAIdlB1bHNlSWQBAAAAA9AAAAkAAAIAAAABAgAAABBpbnZhbGlkIHB1bHNlIGlkAwkBAAAAAiE9AAAAAgkAAfUAAAABBQAAAAZ2Qnl0ZXMJAQAAAAtnZXRIYXNoRGF0YQAAAAEFAAAACHZQdWxzZUlkCQAAAgAAAAECAAAAGGludmFsaWQga2VjY2FrMjU2KHZhbHVlKQYJAAH0AAAAAwgFAAAAAWkAAAAJYm9keUJ5dGVzCQABkQAAAAIIBQAAAAFpAAAABnByb29mcwAAAAAAAAAAAAgFAAAAAWkAAAAPc2VuZGVyUHVibGljS2V5APhsDA== 2 | -------------------------------------------------------------------------------- /cmd/gravity/commands/common.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/urfave/cli/v2" 5 | "go.uber.org/zap" 6 | ) 7 | 8 | const ( 9 | HomeFlag = "home" 10 | LogLevelFlag = "loglevel" 11 | 12 | DbDir = "db" 13 | PrivKeysConfigFileName = "privKey.json" 14 | GenesisFileName = "genesis.json" 15 | LedgerConfigFileName = "config.json" 16 | NodeKeyFileName = "node_key.json" 17 | LedgerKeyStateFileName = "key_state.json" 18 | ) 19 | 20 | func InitLogger(ctx *cli.Context) (*zap.Logger, error) { 21 | level := ctx.String(LogLevelFlag) 22 | logger := &zap.Logger{} 23 | var err error 24 | switch level { 25 | case "development": 26 | logger, err = zap.NewDevelopment() 27 | case "production": 28 | logger, err = zap.NewDevelopment() 29 | } 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | return logger, nil 35 | } 36 | -------------------------------------------------------------------------------- /cmd/gravity/commands/oracle.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "os/signal" 10 | "path" 11 | "syscall" 12 | 13 | "github.com/ethereum/go-ethereum/common/hexutil" 14 | "go.uber.org/zap" 15 | 16 | "github.com/Gravity-Tech/gravity-core/common/account" 17 | "github.com/Gravity-Tech/gravity-core/config" 18 | "github.com/Gravity-Tech/gravity-core/oracle/node" 19 | "github.com/urfave/cli/v2" 20 | ) 21 | 22 | const ( 23 | ConfigFlag = "config" 24 | 25 | DefaultNebulaeDir = "nebulae" 26 | ) 27 | 28 | var ( 29 | OracleCommand = &cli.Command{ 30 | Name: "oracle", 31 | Usage: "", 32 | Description: "Commands to control oracles", 33 | Subcommands: []*cli.Command{ 34 | { 35 | Name: "init", 36 | Usage: "Init oracle node config", 37 | Description: "", 38 | Action: initOracleConfig, 39 | ArgsUsage: " ", 40 | }, 41 | { 42 | Name: "start", 43 | Usage: "Start oracle node", 44 | Description: "", 45 | Action: startOracle, 46 | ArgsUsage: "", 47 | }, 48 | }, 49 | Flags: []cli.Flag{ 50 | &cli.StringFlag{ 51 | Name: HomeFlag, 52 | Value: "./", 53 | Usage: "Home dir for gravity config and files", 54 | }, 55 | &cli.StringFlag{ 56 | Name: LogLevelFlag, 57 | Value: "development", 58 | Usage: "Level of logging, should be development or production", 59 | }, 60 | }, 61 | } 62 | ) 63 | 64 | func initOracleConfig(ctx *cli.Context) error { 65 | home := ctx.String(HomeFlag) 66 | args := ctx.Args() 67 | 68 | if _, err := os.Stat(home); os.IsNotExist(err) { 69 | err = os.Mkdir(home, 0644) 70 | if err != nil { 71 | return err 72 | } 73 | } 74 | 75 | nebulaId := args.Get(0) 76 | chainTypeStr := args.Get(1) 77 | gravityUrl := args.Get(2) 78 | targetChainUrl := args.Get(3) 79 | extractorUrl := args.Get(4) 80 | 81 | cfg := config.OracleConfig{ 82 | TargetChainNodeUrl: targetChainUrl, 83 | GravityNodeUrl: gravityUrl, 84 | ChainId: "S", 85 | ChainType: chainTypeStr, 86 | ExtractorUrl: extractorUrl, 87 | BlocksInterval: 10, 88 | } 89 | b, err := json.MarshalIndent(&cfg, "", " ") 90 | if err != nil { 91 | return err 92 | } 93 | 94 | if _, err := os.Stat(path.Join(home, DefaultNebulaeDir)); os.IsNotExist(err) { 95 | err = os.Mkdir(path.Join(home, DefaultNebulaeDir), 0644) 96 | if err != nil { 97 | return err 98 | } 99 | } 100 | return ioutil.WriteFile(path.Join(home, DefaultNebulaeDir, fmt.Sprintf("%s.json", nebulaId)), b, 0644) 101 | } 102 | 103 | func startOracle(ctx *cli.Context) error { 104 | logger, err := InitLogger(ctx) 105 | if err != nil { 106 | return err 107 | } 108 | defer logger.Sync() // flushes buffer, if any 109 | zap.ReplaceGlobals(logger) 110 | 111 | home := ctx.String(HomeFlag) 112 | nebulaIdStr := ctx.Args().First() 113 | var cfg config.OracleConfig 114 | err = config.ParseConfig(path.Join(home, DefaultNebulaeDir, fmt.Sprintf("%s.json", nebulaIdStr)), &cfg) 115 | if err != nil { 116 | return err 117 | } 118 | 119 | var privKeysCfg config.Keys 120 | err = config.ParseConfig(path.Join(home, PrivKeysConfigFileName), &privKeysCfg) 121 | if err != nil { 122 | return err 123 | } 124 | 125 | validatorPrivKey, err := hexutil.Decode(privKeysCfg.Validator.PrivKey) 126 | if err != nil { 127 | return err 128 | } 129 | 130 | chainType, err := account.ParseChainType(cfg.ChainType) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | nebulaId, err := account.StringToNebulaId(nebulaIdStr, chainType) 136 | if err != nil { 137 | return err 138 | } 139 | 140 | oracleSecretKey, err := account.StringToPrivKey(privKeysCfg.TargetChains[chainType.String()].PrivKey, chainType) 141 | if err != nil { 142 | return err 143 | } 144 | 145 | var chainId byte 146 | if len(cfg.ChainId) > 0 { 147 | chainId = cfg.ChainId[0] 148 | } 149 | sysCtx := context.Background() 150 | oracleNode, err := node.New( 151 | nebulaId, 152 | chainType, 153 | chainId, 154 | oracleSecretKey, 155 | node.NewValidator(validatorPrivKey), 156 | cfg.ExtractorUrl, 157 | cfg.GravityNodeUrl, 158 | cfg.BlocksInterval, 159 | cfg.TargetChainNodeUrl, 160 | sysCtx, 161 | cfg.Custom, 162 | ) 163 | 164 | if err != nil { 165 | return err 166 | } 167 | 168 | err = oracleNode.Init() 169 | if err != nil { 170 | return err 171 | } 172 | 173 | go oracleNode.Start(sysCtx) 174 | 175 | c := make(chan os.Signal, 1) 176 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 177 | <-c 178 | 179 | return nil 180 | } 181 | -------------------------------------------------------------------------------- /cmd/gravity/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/Gravity-Tech/gravity-core/cmd/gravity/commands" 8 | 9 | "github.com/urfave/cli/v2" 10 | ) 11 | 12 | func main() { 13 | 14 | app := &cli.App{ 15 | Name: "Gravity CLI", 16 | Usage: "the gravity command line interface", 17 | Commands: []*cli.Command{ 18 | commands.LedgerCommand, 19 | commands.OracleCommand, 20 | }, 21 | } 22 | 23 | err := app.Run(os.Args) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /common/account/account.go: -------------------------------------------------------------------------------- 1 | package account 2 | 3 | import ( 4 | "github.com/tendermint/tendermint/crypto" 5 | ) 6 | 7 | func Sign(privKey crypto.PrivKey, msg []byte) ([]byte, error) { 8 | return privKey.Sign(msg) 9 | } 10 | 11 | type LedgerValidator struct { 12 | PrivKey crypto.PrivKey 13 | PubKey ConsulPubKey 14 | } 15 | -------------------------------------------------------------------------------- /common/account/chain.go: -------------------------------------------------------------------------------- 1 | package account 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | type ChainType byte 9 | 10 | const ( 11 | Ethereum ChainType = iota 12 | Waves 13 | Binance 14 | Heco 15 | Fantom 16 | Avax 17 | Solana 18 | Polygon 19 | XDai 20 | Okex 21 | ) 22 | 23 | var ( 24 | ErrInvalidChainType = errors.New("invalid chain type") 25 | ErrParseChainType = errors.New("invalid parse chain type") 26 | ) 27 | 28 | func ParseChainType(chainType string) (ChainType, error) { 29 | switch strings.ToLower(chainType) { 30 | case "heco": 31 | return Heco, nil 32 | case "bsc": 33 | return Binance, nil 34 | case "ethereum": 35 | return Ethereum, nil 36 | case "ftm": 37 | return Fantom, nil 38 | case "avax": 39 | return Avax, nil 40 | case "waves": 41 | return Waves, nil 42 | case "solana": 43 | return Solana, nil 44 | case "polygon": 45 | return Polygon, nil 46 | case "xdai": 47 | return XDai, nil 48 | case "okex": 49 | return Okex, nil 50 | default: 51 | return 0, ErrParseChainType 52 | } 53 | } 54 | func (ch ChainType) String() string { 55 | switch ch { 56 | case Ethereum: 57 | return "ethereum" 58 | case Waves: 59 | return "waves" 60 | case Binance: 61 | return "bsc" 62 | case Heco: 63 | return "heco" 64 | case Fantom: 65 | return "ftm" 66 | case Avax: 67 | return "avax" 68 | case Solana: 69 | return "solana" 70 | case Polygon: 71 | return "polygon" 72 | case XDai: 73 | return "xdai" 74 | case Okex: 75 | return "okex" 76 | default: 77 | return "ethereum" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /common/account/nebula.go: -------------------------------------------------------------------------------- 1 | package account 2 | 3 | import ( 4 | "github.com/btcsuite/btcutil/base58" 5 | "github.com/ethereum/go-ethereum/common/hexutil" 6 | "github.com/wavesplatform/gowaves/pkg/crypto" 7 | "go.uber.org/zap" 8 | ) 9 | 10 | const ( 11 | NebulaIdLength = 32 12 | EthereumAddressLength = 20 13 | BSCAddressLength = 20 14 | WavesAddressLength = 26 15 | ) 16 | 17 | type NebulaId [NebulaIdLength]byte 18 | 19 | func StringToNebulaId(address string, chainType ChainType) (NebulaId, error) { 20 | var nebula NebulaId 21 | 22 | switch chainType { 23 | case Ethereum, Binance, Heco, Fantom, Avax, Polygon, XDai, Okex: 24 | nebulaBytes, err := hexutil.Decode(address) 25 | if err != nil { 26 | return NebulaId{}, err 27 | } 28 | nebula = BytesToNebulaId(nebulaBytes) 29 | case Waves: 30 | nebulaBytes := crypto.MustBytesFromBase58(address) 31 | nebula = BytesToNebulaId(nebulaBytes) 32 | case Solana: 33 | nebulaBytes := base58.Decode(address) 34 | nebula = BytesToNebulaId(nebulaBytes) 35 | zap.L().Sugar().Debug("NebulaId: ", nebula) 36 | } 37 | 38 | return nebula, nil 39 | } 40 | func BytesToNebulaId(value []byte) NebulaId { 41 | var idBytes []byte 42 | var id NebulaId 43 | if len(value) < NebulaIdLength { 44 | idBytes = append(idBytes, make([]byte, NebulaIdLength-len(value), NebulaIdLength-len(value))...) 45 | } 46 | idBytes = append(idBytes, value...) 47 | copy(id[:], idBytes) 48 | 49 | return id 50 | } 51 | 52 | func (id NebulaId) ToString(chainType ChainType) string { 53 | nebula := id.ToBytes(chainType) 54 | switch chainType { 55 | case Ethereum, Binance, Heco, Fantom, Avax, Polygon, XDai, Okex: 56 | return hexutil.Encode(nebula[:]) 57 | case Waves: 58 | return base58.Encode(nebula[:]) 59 | case Solana: 60 | return base58.Encode(nebula[:]) 61 | } 62 | 63 | return "" 64 | } 65 | func (id NebulaId) ToBytes(chainType ChainType) []byte { 66 | switch chainType { 67 | case Binance, Heco, Fantom, Avax, Polygon, XDai, Okex: 68 | return id[NebulaIdLength-BSCAddressLength:] 69 | case Ethereum: 70 | return id[NebulaIdLength-EthereumAddressLength:] 71 | case Waves: 72 | return id[NebulaIdLength-WavesAddressLength:] 73 | case Solana: 74 | return id[:] 75 | } 76 | 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /common/account/pubkey.go: -------------------------------------------------------------------------------- 1 | package account 2 | 3 | import ( 4 | "github.com/btcsuite/btcutil/base58" 5 | "github.com/ethereum/go-ethereum/common/hexutil" 6 | solana_common "github.com/portto/solana-go-sdk/common" 7 | "github.com/tendermint/tendermint/crypto/ed25519" 8 | wavesplatform "github.com/wavesplatform/go-lib-crypto" 9 | "github.com/wavesplatform/gowaves/pkg/crypto" 10 | ) 11 | 12 | type ConsulPubKey ed25519.PubKeyEd25519 13 | type OraclesPubKey [33]byte 14 | 15 | func StringToPrivKey(value string, chainType ChainType) ([]byte, error) { 16 | var privKey []byte 17 | var err error 18 | switch chainType { 19 | case Ethereum, Binance, Heco, Fantom, Avax, Polygon, XDai, Okex: 20 | privKey, err = hexutil.Decode(value) 21 | if err != nil { 22 | return nil, err 23 | } 24 | case Solana: 25 | privKey = base58.Decode(value) 26 | case Waves: 27 | wCrypto := wavesplatform.NewWavesCrypto() 28 | seed := wavesplatform.Seed(value) 29 | secret, err := crypto.NewSecretKeyFromBase58(string(wCrypto.PrivateKey(seed))) 30 | if err != nil { 31 | return nil, err 32 | } 33 | privKey = secret.Bytes() 34 | } 35 | 36 | return privKey, nil 37 | } 38 | 39 | func BytesToOraclePubKey(value []byte, chainType ChainType) OraclesPubKey { 40 | var pubKey OraclesPubKey 41 | switch chainType { 42 | case Ethereum, Binance, Heco, Fantom, Avax, Polygon, XDai, Okex: 43 | copy(pubKey[:], value[0:33]) 44 | case Waves: 45 | copy(pubKey[:], append([]byte{0}, value[0:32]...)) 46 | case Solana: 47 | copy(pubKey[:], append([]byte{0}, value[0:32]...)) 48 | } 49 | return pubKey 50 | } 51 | 52 | func (pubKey *OraclesPubKey) ToBytes(chainType ChainType) []byte { 53 | var v []byte 54 | switch chainType { 55 | case Ethereum, Binance, Heco, Fantom, Avax, Polygon, XDai, Okex: 56 | v = pubKey[:33] 57 | case Waves: 58 | v = pubKey[1:33] 59 | case Solana: 60 | v = pubKey[1:33] 61 | } 62 | return v 63 | } 64 | func (pubKey *OraclesPubKey) ToString(chainType ChainType) string { 65 | b := pubKey.ToBytes(chainType) 66 | switch chainType { 67 | case Ethereum, Binance, Heco, Fantom, Avax, Polygon, XDai, Okex: 68 | return hexutil.Encode(b) 69 | case Waves: 70 | return base58.Encode(b) 71 | case Solana: 72 | return base58.Encode(b) 73 | } 74 | 75 | return "" 76 | } 77 | 78 | func StringToOraclePubKey(value string, chainType ChainType) (OraclesPubKey, error) { 79 | var pubKey []byte 80 | var err error 81 | switch chainType { 82 | case Ethereum, Binance, Heco, Fantom, Avax, Polygon, XDai, Okex: 83 | pubKey, err = hexutil.Decode(value) 84 | if err != nil { 85 | return [33]byte{}, err 86 | } 87 | case Waves: 88 | wPubKey, err := crypto.NewPublicKeyFromBase58(value) 89 | pubKey = wPubKey[:] 90 | if err != nil { 91 | return [33]byte{}, err 92 | } 93 | case Solana: 94 | sPubKey := solana_common.PublicKeyFromString(value) 95 | pubKey = sPubKey[:] 96 | if err != nil { 97 | return [33]byte{}, err 98 | } 99 | } 100 | return BytesToOraclePubKey(pubKey, chainType), nil 101 | } 102 | 103 | func HexToValidatorPubKey(hex string) (ConsulPubKey, error) { 104 | b, err := hexutil.Decode(hex) 105 | if err != nil { 106 | return ConsulPubKey{}, err 107 | } 108 | pubKey := ConsulPubKey{} 109 | copy(pubKey[:], b) 110 | return pubKey, nil 111 | } 112 | -------------------------------------------------------------------------------- /common/adaptors/factory.go: -------------------------------------------------------------------------------- 1 | package adaptors 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/Gravity-Tech/gravity-core/abi/ethereum" 8 | "github.com/Gravity-Tech/gravity-core/common/gravity" 9 | "github.com/ethereum/go-ethereum/ethclient" 10 | "github.com/gookit/validate" 11 | wclient "github.com/wavesplatform/gowaves/pkg/client" 12 | ) 13 | 14 | //AdapterOptions - map of custom adaptor creating options 15 | type AdapterOptions map[string]interface{} 16 | 17 | //Factory - abstract factory struct 18 | type Factory struct { 19 | } 20 | 21 | func isGhClientValidator(val interface{}) bool { 22 | switch val.(type) { 23 | case *gravity.Client: 24 | return true 25 | default: 26 | return false 27 | } 28 | } 29 | func isByteValidator(val interface{}) bool { 30 | switch val.(type) { 31 | case byte: 32 | return true 33 | default: 34 | return false 35 | } 36 | } 37 | func isWvClientValidator(val interface{}) bool { 38 | switch val.(type) { 39 | case *wclient.Client: 40 | return true 41 | default: 42 | return false 43 | } 44 | } 45 | func isEthClientValidator(val interface{}) bool { 46 | switch val.(type) { 47 | case *ethclient.Client: 48 | return true 49 | default: 50 | return false 51 | } 52 | } 53 | 54 | func isEthGravityContractValidator(val interface{}) bool { 55 | switch val.(type) { 56 | case *ethereum.Gravity: 57 | return true 58 | default: 59 | return false 60 | } 61 | } 62 | 63 | func NewFactory() *Factory { 64 | validate.AddValidator("isGhClient", isGhClientValidator) 65 | validate.AddValidator("isByte", isByteValidator) 66 | validate.AddValidator("isWvClient", isWvClientValidator) 67 | validate.AddValidator("isEthClient", isEthClientValidator) 68 | validate.AddValidator("isEthGravityContract", isEthGravityContractValidator) 69 | 70 | return &Factory{} 71 | } 72 | 73 | //CreateAdaptor - factory function 74 | func (f *Factory) CreateAdaptor(name string, oracleSecretKey []byte, targetChainNodeUrl string, ctx context.Context, opts AdapterOptions) (IBlockchainAdaptor, error) { 75 | switch name { 76 | case "waves": 77 | return NewWavesAdapterByOpts(oracleSecretKey, targetChainNodeUrl, opts) 78 | case "ethereum": 79 | return NewEthereumsAdapterByOpts(oracleSecretKey, targetChainNodeUrl, ctx, opts) 80 | } 81 | return nil, fmt.Errorf("Unknown adaptor name %s", name) 82 | } 83 | -------------------------------------------------------------------------------- /common/adaptors/factory_test.go: -------------------------------------------------------------------------------- 1 | package adaptors 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/Gravity-Tech/gravity-core/common/gravity" 9 | "github.com/ethereum/go-ethereum/ethclient" 10 | wclient "github.com/wavesplatform/gowaves/pkg/client" 11 | ) 12 | 13 | func stringInSlice(a string, list []string) bool { 14 | for _, b := range list { 15 | if b == a { 16 | return true 17 | } 18 | } 19 | return false 20 | } 21 | 22 | func EqualExcept(a interface{}, b interface{}, ExceptFields []string) bool { 23 | val := reflect.ValueOf(a).Elem() 24 | otherFields := reflect.Indirect(reflect.ValueOf(b)) 25 | 26 | for i := 0; i < val.NumField(); i++ { 27 | typeField := val.Type().Field(i) 28 | if stringInSlice(typeField.Name, ExceptFields) { 29 | continue 30 | } 31 | 32 | value := val.Field(i) 33 | otherValue := otherFields.FieldByName(typeField.Name) 34 | 35 | if value.Interface() != otherValue.Interface() { 36 | return false 37 | } 38 | } 39 | return true 40 | } 41 | func TestNewFactory(t *testing.T) { 42 | tests := []struct { 43 | name string 44 | want *Factory 45 | }{ 46 | {name: "create factory", want: &Factory{}}, 47 | // TODO: Add test cases. 48 | } 49 | for _, tt := range tests { 50 | t.Run(tt.name, func(t *testing.T) { 51 | if got := NewFactory(); !reflect.DeepEqual(got, tt.want) { 52 | t.Errorf("NewFactory() = %v, want %v", got, tt.want) 53 | } 54 | }) 55 | } 56 | } 57 | 58 | func Test_ghClientValidator(t *testing.T) { 59 | type args struct { 60 | val interface{} 61 | } 62 | tests := []struct { 63 | name string 64 | args args 65 | want bool 66 | }{ 67 | {name: "check valid type", args: args{val: &gravity.Client{}}, want: true}, 68 | {name: "check fail type", args: args{val: 4}, want: false}, 69 | // TODO: Add test cases. 70 | } 71 | for _, tt := range tests { 72 | t.Run(tt.name, func(t *testing.T) { 73 | if got := isGhClientValidator(tt.args.val); got != tt.want { 74 | t.Errorf("ghClientValidator() = %v, want %v", got, tt.want) 75 | } 76 | }) 77 | } 78 | } 79 | 80 | func TestFactory_CreateAdaptor(t *testing.T) { 81 | ghClient := gravity.Client{} 82 | wvClient := wclient.Client{} 83 | ethClient := ethclient.Client{} 84 | wanted := WavesAdaptor{ 85 | ghClient: &ghClient, 86 | wavesClient: &wvClient, 87 | chainID: 1, 88 | gravityContract: "contract", 89 | } 90 | validOpts := AdapterOptions{ 91 | "ghClient": &ghClient, 92 | "wvClient": &wvClient, 93 | "gravityContract": "contract", 94 | "chainID": byte(1)} 95 | ethValidOpts := AdapterOptions{ 96 | "ghClient": &ghClient, 97 | "ethClient": ðClient, 98 | "gravityContract": "0x90C52beF8733cDF368Bf8AaD5ee4A78cB68E85"} 99 | f := NewFactory() 100 | type args struct { 101 | name string 102 | oracleSecretKey []byte 103 | targetChainNodeUrl string 104 | opts AdapterOptions 105 | } 106 | tests := []struct { 107 | name string 108 | f *Factory 109 | args args 110 | want IBlockchainAdaptor 111 | wantErr bool 112 | }{ 113 | 114 | {name: "Test valid options", f: f, args: args{name: "waves", oracleSecretKey: []byte("key"), targetChainNodeUrl: "ws:url", opts: validOpts}, wantErr: false, want: &wanted}, 115 | {name: "Test incorrect adaptor name", f: f, args: args{name: "wavesddd", oracleSecretKey: []byte("key"), targetChainNodeUrl: "ws:url", opts: validOpts}, wantErr: true}, 116 | {name: "Test invalid options", f: f, args: args{name: "waves", oracleSecretKey: []byte("key"), targetChainNodeUrl: "ws:url", opts: AdapterOptions{ 117 | "ghClient": &ghClient, 118 | "wvClient": 5, 119 | "gravityContract": "contract", 120 | "chainID": byte(1)}}, wantErr: true}, 121 | {name: "Test ethereum valid options", f: f, args: args{name: "ethereum", oracleSecretKey: []byte("key"), targetChainNodeUrl: "https://ropsten.infura.io/v3/598efca7168947c6a186e2f85b600be1", opts: ethValidOpts}, wantErr: false, want: &wanted}, 122 | {name: "Test ethereum invalid options", f: f, args: args{name: "ethereum", oracleSecretKey: []byte("key"), targetChainNodeUrl: "https://ropsten.infura.io/v3/598efca7168947c6a186e2f85b600be1", opts: AdapterOptions{ 123 | "ghClient": &ghClient, 124 | "wvClient": 5, 125 | "gravityContract": "contract", 126 | "chainID": byte(1)}}, wantErr: true}, 127 | 128 | // TODO: Add test cases. 129 | } 130 | for _, tt := range tests { 131 | t.Run(tt.name, func(t *testing.T) { 132 | f := &Factory{} 133 | _, err := f.CreateAdaptor(tt.args.name, tt.args.oracleSecretKey, tt.args.targetChainNodeUrl, context.Background(), tt.args.opts) 134 | if (err != nil) != tt.wantErr { 135 | t.Errorf("Factory.CreateAdaptor() error = %v, wantErr %v", err, tt.wantErr) 136 | return 137 | } 138 | 139 | }) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /common/adaptors/interface.go: -------------------------------------------------------------------------------- 1 | package adaptors 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/Gravity-Tech/gravity-core/abi" 7 | "github.com/Gravity-Tech/gravity-core/oracle/extractor" 8 | 9 | "github.com/Gravity-Tech/gravity-core/common/account" 10 | ) 11 | 12 | type IBlockchainAdaptor interface { 13 | GetHeight(ctx context.Context) (uint64, error) 14 | WaitTx(id string, ctx context.Context) error 15 | Sign(msg []byte) ([]byte, error) 16 | PubKey() account.OraclesPubKey 17 | ValueType(nebulaId account.NebulaId, ctx context.Context) (abi.ExtractorType, error) 18 | 19 | AddPulse(nebulaId account.NebulaId, pulseId uint64, validators []account.OraclesPubKey, hash []byte, ctx context.Context) (string, error) 20 | SendValueToSubs(nebulaId account.NebulaId, pulseId uint64, value *extractor.Data, ctx context.Context) error 21 | 22 | SetOraclesToNebula(nebulaId account.NebulaId, oracles []*account.OraclesPubKey, signs map[account.OraclesPubKey][]byte, round int64, ctx context.Context) (string, error) 23 | SendConsulsToGravityContract(newConsulsAddresses []*account.OraclesPubKey, signs map[account.OraclesPubKey][]byte, round int64, ctx context.Context) (string, error) 24 | SignConsuls(consulsAddresses []*account.OraclesPubKey, roundId int64, sender account.OraclesPubKey) ([]byte, error) 25 | SignOracles(nebulaId account.NebulaId, oracles []*account.OraclesPubKey, round int64, sender account.OraclesPubKey) ([]byte, error) 26 | SignHash(nebulaId account.NebulaId, intervalId uint64, pulseId uint64, hash []byte) ([]byte, error) 27 | LastPulseId(nebulaId account.NebulaId, ctx context.Context) (uint64, error) 28 | LastRound(ctx context.Context) (uint64, error) 29 | RoundExist(roundId int64, ctx context.Context) (bool, error) 30 | } 31 | -------------------------------------------------------------------------------- /common/adaptors/waves_test.go: -------------------------------------------------------------------------------- 1 | package adaptors 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Gravity-Tech/gravity-core/common/gravity" 7 | wclient "github.com/wavesplatform/gowaves/pkg/client" 8 | ) 9 | 10 | func TestWavesAdaptor_applyOpts(t *testing.T) { 11 | f := NewFactory() 12 | if f != nil { 13 | } 14 | 15 | ghClient := gravity.Client{} 16 | wvClient := wclient.Client{} 17 | wanted := WavesAdaptor{ 18 | ghClient: &ghClient, 19 | wavesClient: &wvClient, 20 | chainID: 1, 21 | gravityContract: "contract", 22 | } 23 | validOpts := AdapterOptions{ 24 | "ghClient": &ghClient, 25 | "wvClient": &wvClient, 26 | "gravityContract": "contract", 27 | "chainID": byte(1)} 28 | 29 | type args struct { 30 | opts AdapterOptions 31 | } 32 | tests := []struct { 33 | name string 34 | args args 35 | wantErr bool 36 | want WavesAdaptor 37 | }{ 38 | {name: "Test valid options", args: args{opts: validOpts}, wantErr: false, want: wanted}, 39 | {name: "Test invalid ghClient", args: args{opts: AdapterOptions{ 40 | "ghClient": 6, 41 | "wvClient": &wvClient, 42 | "gravityContract": "contract", 43 | "chainID": 1}}, wantErr: true}, 44 | {name: "Test invaid wClient", args: args{opts: AdapterOptions{ 45 | "ghClient": &ghClient, 46 | "wvClient": 5, 47 | "gravityContract": "contract", 48 | "chainID": 1}}, wantErr: true}, 49 | {name: "Test invaid chainID", args: args{opts: AdapterOptions{ 50 | "ghClient": &ghClient, 51 | "wvClient": 5, 52 | "gravityContract": "contract", 53 | "chainID": "karamba"}}, wantErr: true}, 54 | {name: "Test invaid gravityContract", args: args{opts: AdapterOptions{ 55 | "ghClient": &ghClient, 56 | "wvClient": 5, 57 | "gravityContract": 56, 58 | "chainID": 1}}, wantErr: true}, 59 | 60 | // TODO: Add test cases. 61 | } 62 | for _, tt := range tests { 63 | t.Run(tt.name, func(t *testing.T) { 64 | wa := &WavesAdaptor{} 65 | if err := wa.applyOpts(tt.args.opts); (err != nil) != tt.wantErr { 66 | t.Errorf("WavesAdaptor.applyOpts() error = %v, wantErr %v", err, tt.wantErr) 67 | } else if !tt.wantErr { 68 | if *wa != tt.want { 69 | t.Errorf("ghClientValidator() = %v, want %v", *wa, tt.want) 70 | } 71 | } 72 | }) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /common/gravity/client_test.go: -------------------------------------------------------------------------------- 1 | package gravity 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "testing" 7 | 8 | "github.com/Gravity-Tech/gravity-core/common/storage" 9 | "github.com/Gravity-Tech/gravity-core/ledger/query" 10 | rpchttp "github.com/tendermint/tendermint/rpc/client/http" 11 | ) 12 | 13 | func TestNebulaInfo(t *testing.T) { 14 | rq := query.ByNebulaRq{ 15 | ChainType: 6, 16 | NebulaAddress: "4VL4hsSPPNdqP5ajXinJ3L434uycugBxYaJiJ2Zv4FPo", 17 | } 18 | var rqi interface{} 19 | rqi = rq 20 | 21 | var err error 22 | var b []byte 23 | b, ok := rqi.([]byte) 24 | if !ok { 25 | b, err = json.Marshal(rq) 26 | if err != nil { 27 | log.Print("KARAMBA 1") 28 | log.Print(err) 29 | t.FailNow() 30 | } 31 | } 32 | client, err := rpchttp.New("http://localhost:26657", "/websocket") 33 | if err != nil { 34 | log.Print("KARAMBA 2") 35 | log.Print(err) 36 | t.FailNow() 37 | } 38 | 39 | rs, err := client.ABCIQuery(string("nebula_info"), b) 40 | if err != nil { 41 | log.Print("KARAMBA 3") 42 | log.Print(err) 43 | t.FailNow() 44 | } 45 | 46 | nebulaCustomParams := storage.NebulaCustomParams{} 47 | log.Print("RESULT") 48 | log.Print(rs.Response.Value) 49 | err = json.Unmarshal(rs.Response.Value, &nebulaCustomParams) 50 | if err != nil { 51 | log.Print("KARAMBA 4") 52 | log.Print(err) 53 | t.FailNow() 54 | } 55 | 56 | log.Print("KARAMBA 5") 57 | log.Print(nebulaCustomParams) 58 | t.FailNow() 59 | } 60 | 61 | func TestNebulaCustomParams(t *testing.T) { 62 | rq := query.ByNebulaRq{ 63 | ChainType: 6, 64 | NebulaAddress: "4VL4hsSPPNdqP5ajXinJ3L434uycugBxYaJiJ2Zv4FPo", 65 | } 66 | var rqi interface{} 67 | rqi = rq 68 | 69 | var err error 70 | var b []byte 71 | b, ok := rqi.([]byte) 72 | if !ok { 73 | b, err = json.Marshal(rq) 74 | if err != nil { 75 | log.Print("KARAMBA 1") 76 | log.Print(err) 77 | t.FailNow() 78 | } 79 | } 80 | client, err := rpchttp.New("http://localhost:26657", "/websocket") 81 | if err != nil { 82 | log.Print("KARAMBA 2") 83 | log.Print(err) 84 | t.FailNow() 85 | } 86 | 87 | rs, err := client.ABCIQuery(string("nebulaCustomParams"), b) 88 | if err != nil { 89 | log.Print("KARAMBA 3") 90 | log.Print(err) 91 | t.FailNow() 92 | } 93 | 94 | nebulaCustomParams := storage.NebulaCustomParams{} 95 | log.Print("RESULT") 96 | log.Print(rs.Response.Value) 97 | err = json.Unmarshal(rs.Response.Value, &nebulaCustomParams) 98 | if err != nil { 99 | log.Print("KARAMBA 4") 100 | log.Print(err) 101 | t.FailNow() 102 | } 103 | 104 | log.Print("KARAMBA 5") 105 | log.Print(nebulaCustomParams) 106 | t.FailNow() 107 | } 108 | -------------------------------------------------------------------------------- /common/hashing/hash.go: -------------------------------------------------------------------------------- 1 | package hashing 2 | 3 | import ( 4 | "crypto/sha256" 5 | 6 | "github.com/Gravity-Tech/gravity-core/common/account" 7 | "github.com/ethereum/go-ethereum/crypto" 8 | ) 9 | 10 | 11 | func WrappedKeccak256(input []byte, chain account.ChainType) []byte { 12 | if chain != account.Solana { 13 | return crypto.Keccak256(input[:]) 14 | } 15 | 16 | digest := sha256.Sum256(input[:]) 17 | return digest[:] 18 | } -------------------------------------------------------------------------------- /common/helpers/state.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | type States []State 4 | type State struct { 5 | Key string `json:"key"` 6 | Value interface{} `json:"value"` 7 | Type string `json:"type"` 8 | } 9 | 10 | func (states States) Map() map[string]State { 11 | stateMap := make(map[string]State) 12 | for _, v := range states { 13 | stateMap[v.Key] = v 14 | } 15 | return stateMap 16 | } 17 | -------------------------------------------------------------------------------- /common/helpers/waves.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/wavesplatform/gowaves/pkg/crypto" 11 | 12 | "github.com/wavesplatform/gowaves/pkg/client" 13 | ) 14 | 15 | const ( 16 | GetStateByAddressPath = "addresses/data" 17 | 18 | TxWaitCount = 10 19 | BlockWaitCount = 30 20 | ) 21 | 22 | type ClientHelper struct { 23 | client *client.Client 24 | } 25 | 26 | func NewClientHelper(client *client.Client) ClientHelper { 27 | return ClientHelper{client: client} 28 | } 29 | 30 | func (helper *ClientHelper) GetStateByAddressAndKey(address string, key string, ctx context.Context) (*State, *client.Response, error) { 31 | url := fmt.Sprintf("%s/%s/%s?key=%s", helper.client.GetOptions().BaseUrl, GetStateByAddressPath, address, key) 32 | 33 | req, err := http.NewRequest("GET", url, nil) 34 | if err != nil { 35 | return nil, nil, err 36 | } 37 | 38 | var out []State 39 | response, err := helper.client.Do(ctx, req, &out) 40 | if err != nil { 41 | return nil, response, err 42 | } 43 | 44 | if len(out) == 0 { 45 | return nil, response, nil //fmt.Errorf("States array is empty") 46 | } 47 | return &out[0], response, nil 48 | } 49 | 50 | func (helper *ClientHelper) WaitTx(id string, ctx context.Context) <-chan error { 51 | out := make(chan error) 52 | idDig := crypto.MustDigestFromBase58(id) 53 | go func() { 54 | defer close(out) 55 | for i := 0; i <= TxWaitCount; i++ { 56 | _, res, err := helper.client.Transactions.UnconfirmedInfo(ctx, idDig) 57 | if err != nil && res == nil { 58 | out <- err 59 | break 60 | } 61 | 62 | if res.StatusCode != http.StatusOK { 63 | _, res, err := helper.client.Transactions.Info(ctx, idDig) 64 | if err != nil && res == nil { 65 | out <- err 66 | break 67 | } 68 | 69 | if res.StatusCode != http.StatusOK { 70 | out <- errors.New("tx not found") 71 | break 72 | } else { 73 | break 74 | } 75 | } 76 | 77 | if TxWaitCount == i { 78 | out <- errors.New("tx not found") 79 | break 80 | } 81 | time.Sleep(time.Second) 82 | } 83 | }() 84 | return out 85 | } 86 | func (helper *ClientHelper) WaitByHeight(height uint64, ctx context.Context) <-chan error { 87 | out := make(chan error) 88 | go func() { 89 | defer close(out) 90 | for i := 0; i <= BlockWaitCount; i++ { 91 | currentHeight, _, err := helper.client.Blocks.Height(ctx) 92 | if err != nil { 93 | out <- err 94 | break 95 | } 96 | 97 | if currentHeight.Height >= height { 98 | break 99 | } 100 | 101 | if BlockWaitCount == i { 102 | out <- errors.New("block not found") 103 | break 104 | } 105 | 106 | time.Sleep(10 * time.Second) 107 | } 108 | }() 109 | return out 110 | } 111 | -------------------------------------------------------------------------------- /common/score/calculator.go: -------------------------------------------------------------------------------- 1 | package score 2 | 3 | import ( 4 | "github.com/Gravity-Tech/gravity-core/common/account" 5 | "github.com/Gravity-Tech/gravity-core/common/score/trustgraph" 6 | "github.com/Gravity-Tech/gravity-core/common/storage" 7 | ) 8 | 9 | const ( 10 | Accuracy = 100 11 | ) 12 | 13 | func UInt64ToFloat32Score(score uint64) float32 { 14 | return float32(score) / Accuracy 15 | } 16 | func Float32ToUInt64Score(score float32) uint64 { 17 | return uint64(score * Accuracy) 18 | } 19 | 20 | type Actor struct { 21 | Name account.ConsulPubKey 22 | InitScore uint64 23 | } 24 | 25 | func Calculate(initScores storage.ScoresByConsulMap, votes storage.VoteByConsulMap) (storage.ScoresByConsulMap, error) { 26 | group := trustgraph.NewGroup() 27 | 28 | var newValidators []int 29 | idByValidator := make(map[account.ConsulPubKey]int) 30 | validatorById := make(map[int]account.ConsulPubKey) 31 | 32 | index := 0 33 | for k, v := range initScores { 34 | idByValidator[k] = index 35 | validatorById[index] = k 36 | err := group.InitialTrust(idByValidator[k], UInt64ToFloat32Score(v)) 37 | if err != nil { 38 | return nil, err 39 | } 40 | index++ 41 | } 42 | 43 | for voter, _ := range initScores { 44 | existVote := make(map[account.ConsulPubKey]bool) 45 | for _, vote := range votes[voter] { 46 | if voter == vote.PubKey { 47 | continue 48 | } 49 | if _, ok := idByValidator[vote.PubKey]; !ok { 50 | idByValidator[vote.PubKey] = index 51 | validatorById[index] = vote.PubKey 52 | err := group.InitialTrust(idByValidator[vote.PubKey], 0) 53 | if err != nil { 54 | return nil, err 55 | } 56 | newValidators = append(newValidators, index) 57 | index++ 58 | } 59 | err := group.Add(idByValidator[voter], idByValidator[vote.PubKey], UInt64ToFloat32Score(vote.Score)) 60 | if err != nil { 61 | return nil, err 62 | } 63 | existVote[vote.PubKey] = true 64 | } 65 | for validator, _ := range initScores { 66 | if existVote[validator] || voter == validator { 67 | continue 68 | } 69 | 70 | err := group.Add(idByValidator[voter], idByValidator[validator], UInt64ToFloat32Score(initScores[validator])) 71 | if err != nil { 72 | return nil, err 73 | } 74 | } 75 | } 76 | for _, v := range newValidators { 77 | for validator, _ := range initScores { 78 | err := group.Add(v, idByValidator[validator], UInt64ToFloat32Score(initScores[validator])) 79 | if err != nil { 80 | return nil, err 81 | } 82 | } 83 | } 84 | 85 | out := group.Compute() 86 | 87 | score := make(storage.ScoresByConsulMap) 88 | for i, v := range out { 89 | score[validatorById[i]] = Float32ToUInt64Score(v) 90 | } 91 | return score, nil 92 | } 93 | -------------------------------------------------------------------------------- /common/score/calculator_test.go: -------------------------------------------------------------------------------- 1 | package score 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | "time" 7 | 8 | "github.com/Gravity-Tech/gravity-core/common/account" 9 | 10 | "github.com/Gravity-Tech/gravity-core/common/storage" 11 | ) 12 | 13 | func TestUInt64ToFloat32Score(t *testing.T) { 14 | generator := rand.New(rand.NewSource(time.Now().Unix())) 15 | score := uint64(generator.Intn(100)) 16 | v := UInt64ToFloat32Score(score) 17 | if uint64(v*Accuracy) != score { 18 | t.Error("invalid convert uint to float score") 19 | } 20 | } 21 | func TestFloat32ToUInt64Score(t *testing.T) { 22 | generator := rand.New(rand.NewSource(time.Now().Unix())) 23 | score := float32(generator.Intn(100)) / 100 24 | v := Float32ToUInt64Score(score) 25 | if float32(v)/Accuracy != score { 26 | t.Error("invalid convert float to uint score") 27 | } 28 | } 29 | 30 | func TestCalculateDropValidator(t *testing.T) { 31 | consuls := []account.ConsulPubKey{ 32 | account.ConsulPubKey([32]byte{0}), 33 | account.ConsulPubKey([32]byte{1}), 34 | account.ConsulPubKey([32]byte{2}), 35 | account.ConsulPubKey([32]byte{3}), 36 | account.ConsulPubKey([32]byte{4}), 37 | } 38 | 39 | initScores := storage.ScoresByConsulMap{ 40 | consuls[0]: Accuracy, 41 | consuls[1]: Accuracy, 42 | consuls[2]: Accuracy, 43 | consuls[3]: Accuracy, 44 | consuls[4]: Accuracy, 45 | } 46 | 47 | votes := storage.VoteByConsulMap{ 48 | consuls[0]: []storage.Vote{ 49 | {consuls[1], Accuracy}, 50 | {consuls[2], Accuracy}, 51 | {consuls[3], Accuracy}, 52 | {consuls[4], 0}, 53 | }, 54 | consuls[1]: []storage.Vote{ 55 | {consuls[0], Accuracy}, 56 | {consuls[2], Accuracy}, 57 | {consuls[3], Accuracy}, 58 | {consuls[4], 0}, 59 | }, 60 | consuls[2]: []storage.Vote{ 61 | {consuls[0], Accuracy}, 62 | {consuls[1], Accuracy}, 63 | {consuls[3], Accuracy}, 64 | {consuls[4], 0}, 65 | }, 66 | consuls[3]: []storage.Vote{ 67 | {consuls[0], Accuracy}, 68 | {consuls[1], Accuracy}, 69 | {consuls[2], Accuracy}, 70 | {consuls[4], 0}, 71 | }, 72 | consuls[4]: []storage.Vote{ 73 | {consuls[0], Accuracy}, 74 | {consuls[1], Accuracy}, 75 | {consuls[2], Accuracy}, 76 | {consuls[3], Accuracy}, 77 | }, 78 | } 79 | 80 | score, err := Calculate(initScores, votes) 81 | if err != nil { 82 | t.Error(err) 83 | } 84 | 85 | if score[consuls[0]] != Accuracy { 86 | t.Error("invalid consul #1 score") 87 | } else if score[consuls[1]] != Accuracy { 88 | t.Error("invalid consul #2 score") 89 | } else if score[consuls[2]] != Accuracy { 90 | t.Error("invalid consul #3 score") 91 | } else if score[consuls[3]] != Accuracy { 92 | t.Error("invalid consul #4 score") 93 | } else if score[consuls[4]] != 0 { 94 | t.Error("invalid consul #5 score") 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /common/score/trustgraph/trustgraph.go: -------------------------------------------------------------------------------- 1 | // Package trustGraph is based on EigenTrust 2 | // http://nlp.stanford.edu/pubs/eigentrust.pdf 3 | package trustgraph 4 | 5 | import ( 6 | "errors" 7 | ) 8 | 9 | // Group represents a group of peers. Peers need to be given unique, int IDs. 10 | // Certainty represents the threshold of RMS change at which the algorithm will 11 | // escape. Max is the maximum number of loos the algorithm will perform before 12 | // escaping (regardless of certainty). These default to 0.001 and 200 13 | // respectivly and generally don't need to be changed. 14 | type Group struct { 15 | trustGrid map[int]map[int]float32 16 | initialTrust map[int]float32 17 | Certainty float32 18 | Max int 19 | Alpha float32 20 | } 21 | 22 | // NewGroup is the constructor for Group. 23 | func NewGroup() Group { 24 | return Group{ 25 | trustGrid: map[int]map[int]float32{}, 26 | initialTrust: map[int]float32{}, 27 | Certainty: 0.0001, 28 | Max: 200, 29 | Alpha: 1, 30 | } 31 | } 32 | 33 | // Add will add or override a trust relationship. The first arg is the peer who 34 | // is extending trust, the second arg is the peer being trusted (by the peer 35 | // in the first arg). The 3rd arg is the amount of trust, which must be 36 | func (g Group) Add(truster, trusted int, amount float32) (err error) { 37 | err = float32InRange(amount) 38 | if err == nil { 39 | a, ok := g.trustGrid[truster] 40 | if !ok { 41 | a = map[int]float32{} 42 | g.trustGrid[truster] = a 43 | } 44 | a[trusted] = amount 45 | } 46 | return 47 | } 48 | 49 | // InitialTrust sets the vaulues used to seed the calculation as well as the 50 | // corrective factor used by Alpha. 51 | func (g Group) InitialTrust(trusted int, amount float32) (err error) { 52 | err = float32InRange(amount) 53 | if err == nil { 54 | g.initialTrust[trusted] = amount 55 | } 56 | return 57 | } 58 | 59 | // float32InRange is a helper to check that a value is 0.0 <= x <= 1.0 60 | func float32InRange(x float32) error { 61 | if x < 0 { 62 | return errors.New("Trust amount cannot be less than 0") 63 | } 64 | if x > 1 { 65 | return errors.New("Trust amount cannot be greater than 1") 66 | } 67 | return nil 68 | } 69 | 70 | // Compute will approximate the trustworthyness of each peer from the 71 | // information known of how much peers trust eachother. 72 | // It wil loop, upto g.Max times or until the average difference between 73 | // iterations is less than g.Certainty. 74 | func (g Group) Compute() map[int]float32 { 75 | if len(g.initialTrust) == 0 { 76 | return map[int]float32{} 77 | } 78 | t0 := g.initialTrust //trust map for previous iteration 79 | 80 | for i := 0; i < g.Max; i++ { 81 | t1 := *g.computeIteration(&t0) // trust map for current iteration 82 | d := avgD(&t0, &t1) 83 | t0 = t1 84 | if d < g.Certainty { 85 | break 86 | } 87 | } 88 | 89 | return t0 90 | } 91 | 92 | // computeIteration is broken out of Compute to aid comprehension. It is the 93 | // inner loop of Compute. It loops over every value in t (the current trust map) 94 | // and looks up how much trust that peer extends to every other peer. The 95 | // product of the direct trust and indirect trust 96 | func (g Group) computeIteration(t0 *map[int]float32) *map[int]float32 { 97 | 98 | t1 := map[int]float32{} 99 | for truster, directTrust := range *t0 { 100 | for trusted, indirectTrust := range g.trustGrid[truster] { 101 | if trusted != truster { 102 | t1[trusted] += directTrust * indirectTrust 103 | } 104 | } 105 | } 106 | 107 | // normalize the trust values 108 | // in the EigenTrust paper, this was not done every step, but I prefer to 109 | // Not doing it means the diff (d) needs to be normalized in 110 | // proportion to the values (because they increase with every iteration) 111 | highestTrust := float32(0) 112 | for _, v := range t1 { 113 | if v > highestTrust { 114 | highestTrust = v 115 | } 116 | } 117 | //Todo handle highestTrust == 0 118 | for i, v := range t1 { 119 | t1[i] = (v/highestTrust)*g.Alpha + (1-g.Alpha)*g.initialTrust[i] 120 | } 121 | 122 | return &t1 123 | } 124 | 125 | // abs is helper to take abs of float32 126 | func abs(x float32) float32 { 127 | if x < 0 { 128 | return -x 129 | } 130 | return x 131 | } 132 | 133 | // avgD is helper to compare 2 maps of float32s and return the average 134 | // difference between them 135 | func avgD(t0, t1 *map[int]float32) float32 { 136 | d := float32(0) 137 | for i, v := range *t1 { 138 | d += abs(v - (*t0)[i]) 139 | } 140 | d = d / float32(len(*t0)) 141 | return d 142 | } 143 | -------------------------------------------------------------------------------- /common/score/trustgraph/trustgraph_test.go: -------------------------------------------------------------------------------- 1 | package trustgraph 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestRand(t *testing.T) { 11 | peers := 200 12 | rand.Seed(time.Now().UTC().UnixNano()) 13 | g := NewGroup() 14 | 15 | //randomly set actual trust values for peers 16 | actualTrust := make([]float32, peers) 17 | for i := 0; i < peers; i++ { 18 | actualTrust[i] = rand.Float32() 19 | } 20 | 21 | // peer0 is set to and granted 100% trust 22 | actualTrust[0] = 1 23 | g.InitialTrust(0, 1) 24 | 25 | // set 30% of trust values to +/- 10% of actual trust 26 | for i := 0; i < peers; i++ { 27 | for j := 0; j < peers; j++ { 28 | if rand.Float32() > .7 { 29 | g.Add(i, j, randNorm(actualTrust[j])) 30 | } 31 | } 32 | } 33 | 34 | // compute trust 35 | out := g.Compute() 36 | 37 | // find RMS error 38 | e := float32(0) 39 | for i := 0; i < peers; i++ { 40 | x := actualTrust[i] - out[i] 41 | e += x * x 42 | } 43 | e = float32(math.Sqrt(float64(e / float32(peers)))) 44 | 45 | if e > .2 { 46 | t.Error("RMS Error should be less than 20% for a 30% full trust grid of 200 nodes") 47 | } 48 | } 49 | 50 | // randNorm takes a float and returns a value within +/- 10%, 51 | // without going over 1 52 | func randNorm(x float32) float32 { 53 | r := rand.Float32()*.2 + .9 54 | x *= r 55 | if x > 1 { 56 | return 1 57 | } 58 | return x 59 | } 60 | 61 | func TestRangeError(t *testing.T) { 62 | g := NewGroup() 63 | 64 | err := g.Add(1, 2, 1.1) 65 | if err.Error() != "Trust amount cannot be greater than 1" { 66 | t.Error("Expected error") 67 | } 68 | 69 | err = g.Add(1, 2, -1) 70 | if err.Error() != "Trust amount cannot be less than 0" { 71 | t.Error("Expected error less than 0 error") 72 | } 73 | 74 | err = g.Add(1, 2, 1) 75 | if err != nil { 76 | t.Error("Did not expected error") 77 | } 78 | 79 | err = g.Add(1, 2, 0) 80 | if err != nil { 81 | t.Error("Did not expected error") 82 | } 83 | 84 | err = g.Add(1, 2, 0.5) 85 | if err != nil { 86 | t.Error("Did not expected error") 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /common/state/state_test.go: -------------------------------------------------------------------------------- 1 | package state 2 | 3 | import "testing" 4 | 5 | func TestCalculateSubRound(t *testing.T) { 6 | type args struct { 7 | tcHeight uint64 8 | blocksInterval uint64 9 | } 10 | tests := []struct { 11 | name string 12 | args args 13 | want SubRound 14 | }{ 15 | 16 | {"test1", args{2788569, 20}, 0}, 17 | {"test2", args{2788570, 20}, 1}, 18 | {"test3", args{2788571, 20}, 1}, 19 | {"test4", args{2788572, 20}, 1}, 20 | 21 | {"test5", args{2788579, 20}, 1}, 22 | 23 | // TODO: Add test cases. 24 | } 25 | for _, tt := range tests { 26 | t.Run(tt.name, func(t *testing.T) { 27 | if got := CalculateSubRound(tt.args.tcHeight, tt.args.blocksInterval); got != tt.want { 28 | t.Errorf("CalculateSubRound() = %v, want %v", got, tt.want) 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /common/storage/commit.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Gravity-Tech/gravity-core/common/account" 7 | "go.uber.org/zap" 8 | 9 | "github.com/ethereum/go-ethereum/common/hexutil" 10 | ) 11 | 12 | func formCommitKey(nebulaId account.NebulaId, tcHeight int64, pulseId int64, oraclePubKey account.OraclesPubKey) []byte { 13 | return formKey(string(CommitKey), hexutil.Encode(nebulaId[:]), fmt.Sprintf("%d", tcHeight), fmt.Sprintf("%d", pulseId), hexutil.Encode(oraclePubKey[:])) 14 | } 15 | 16 | func (storage *Storage) CommitHash(nebulaId account.NebulaId, tcHeight int64, pulseId int64, oraclePubKey account.OraclesPubKey) ([]byte, error) { 17 | zap.L().Sugar().Debugf("CommitHash key: %s", formCommitKey(nebulaId, tcHeight, pulseId, oraclePubKey)) 18 | b, err := storage.getValue(formCommitKey(nebulaId, tcHeight, pulseId, oraclePubKey)) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | return b, err 24 | } 25 | 26 | func (storage *Storage) SetCommitHash(nebulaId account.NebulaId, tcHeight int64, pulseId int64, oraclePubKey account.OraclesPubKey, commit []byte) error { 27 | zap.L().Sugar().Debugf("SetCommitHash key: %s", formCommitKey(nebulaId, tcHeight, pulseId, oraclePubKey)) 28 | return storage.setValue(formCommitKey(nebulaId, tcHeight, pulseId, oraclePubKey), commit) 29 | } 30 | -------------------------------------------------------------------------------- /common/storage/consuls.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/ethereum/go-ethereum/common/hexutil" 8 | 9 | "github.com/Gravity-Tech/gravity-core/common/account" 10 | ) 11 | 12 | type Consul struct { 13 | PubKey account.ConsulPubKey 14 | Value uint64 15 | } 16 | 17 | func formSignConsulsByConsulKey(pubKey account.ConsulPubKey, chainType account.ChainType, roundId int64) []byte { 18 | prefix := "" 19 | switch chainType { 20 | case account.Waves: 21 | prefix = "waves" 22 | case account.Ethereum: 23 | prefix = "ethereum" 24 | case account.Binance: 25 | prefix = "bsc" 26 | case account.Fantom: 27 | prefix = "ftm" 28 | case account.Heco: 29 | prefix = "heco" 30 | case account.Avax: 31 | prefix = "avax" 32 | case account.Solana: 33 | prefix = "solana" 34 | case account.Polygon: 35 | prefix = "polygon" 36 | case account.XDai: 37 | prefix = "xdai" 38 | case account.Okex: 39 | prefix = "okex" 40 | } 41 | return formKey(string(SignConsulsResultByConsulKey), hexutil.Encode(pubKey[:]), prefix, fmt.Sprintf("%d", roundId)) 42 | } 43 | 44 | func (storage *Storage) Consuls() ([]Consul, error) { 45 | var consuls []Consul 46 | 47 | key := []byte(ConsulsKey) 48 | item, err := storage.txn.Get(key) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | b, err := item.ValueCopy(nil) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | err = json.Unmarshal(b, &consuls) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | return consuls, err 64 | } 65 | func (storage *Storage) SetConsuls(consuls []Consul) error { 66 | return storage.setValue([]byte(ConsulsKey), consuls) 67 | } 68 | 69 | func (storage *Storage) ConsulsCandidate() ([]Consul, error) { 70 | var consuls []Consul 71 | 72 | key := []byte(ConsulsCandidateKey) 73 | item, err := storage.txn.Get(key) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | b, err := item.ValueCopy(nil) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | err = json.Unmarshal(b, &consuls) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | return consuls, err 89 | } 90 | func (storage *Storage) SetConsulsCandidate(consuls []Consul) error { 91 | return storage.setValue([]byte(ConsulsCandidateKey), consuls) 92 | } 93 | 94 | func (storage *Storage) SignConsulsByConsul(consulPubKey account.ConsulPubKey, chainType account.ChainType, roundId int64) ([]byte, error) { 95 | key := formSignConsulsByConsulKey(consulPubKey, chainType, roundId) 96 | item, err := storage.txn.Get(key) 97 | if err != nil { 98 | return nil, err 99 | } 100 | 101 | b, err := item.ValueCopy(nil) 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | return b, err 107 | } 108 | func (storage *Storage) SetSignConsuls(consulsPubKey account.ConsulPubKey, chainType account.ChainType, roundId int64, sign []byte) error { 109 | return storage.setValue(formSignConsulsByConsulKey(consulsPubKey, chainType, roundId), sign) 110 | } 111 | -------------------------------------------------------------------------------- /common/storage/nebula.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | 7 | "github.com/dgraph-io/badger" 8 | "go.uber.org/zap" 9 | 10 | "github.com/ethereum/go-ethereum/common/hexutil" 11 | 12 | "github.com/Gravity-Tech/gravity-core/common/account" 13 | ) 14 | 15 | type NebulaMap map[string]NebulaInfo 16 | type NebulaInfo struct { 17 | MaxPulseCountInBlock uint64 18 | MinScore uint64 19 | ChainType account.ChainType 20 | Owner account.ConsulPubKey 21 | } 22 | 23 | type NebulaCustomParamsMap map[string]NebulaCustomParams 24 | type NebulaCustomParams map[string]interface{} 25 | 26 | func parseNebulaInfoKey(value []byte) (account.NebulaId, error) { 27 | hex := []byte(strings.Split(string(value), Separator)[2]) 28 | key, err := hexutil.Decode(string(hex)) 29 | if err != nil { 30 | return [32]byte{}, err 31 | } 32 | var pubKey account.NebulaId 33 | copy(pubKey[:], key[:]) 34 | return pubKey, nil 35 | } 36 | func formNebulaInfoKey(nebulaId account.NebulaId) []byte { 37 | return formKey(string(NebulaInfoKey), hexutil.Encode(nebulaId[:])) 38 | } 39 | 40 | func (storage *Storage) Nebulae() (NebulaMap, error) { 41 | it := storage.txn.NewIterator(badger.DefaultIteratorOptions) 42 | defer it.Close() 43 | 44 | prefix := []byte(NebulaInfoKey) 45 | nebulaeInfo := make(NebulaMap) 46 | for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() { 47 | item := it.Item() 48 | k := item.Key() 49 | err := item.Value(func(v []byte) error { 50 | var nebulaInfo NebulaInfo 51 | err := json.Unmarshal(v, &nebulaInfo) 52 | if err != nil { 53 | return err 54 | } 55 | pubKey, err := parseNebulaInfoKey(k) 56 | if err != nil { 57 | return err 58 | } 59 | nebulaeInfo[pubKey.ToString(nebulaInfo.ChainType)] = nebulaInfo 60 | return nil 61 | }) 62 | if err != nil { 63 | return nil, err 64 | } 65 | } 66 | 67 | return nebulaeInfo, nil 68 | } 69 | func (storage *Storage) NebulaInfo(nebulaId account.NebulaId) (*NebulaInfo, error) { 70 | b, err := storage.getValue(formNebulaInfoKey(nebulaId)) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | var nebulae NebulaInfo 76 | err = json.Unmarshal(b, &nebulae) 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | return &nebulae, err 82 | } 83 | 84 | func (storage *Storage) DropNebula(nebulaId account.NebulaId) error { 85 | return storage.dropValue(formNebulaInfoKey(nebulaId)) 86 | } 87 | 88 | func (storage *Storage) SetNebula(nebulaId account.NebulaId, info NebulaInfo) error { 89 | zap.L().Debug("Setting nebula!!!!") 90 | return storage.setValue(formNebulaInfoKey(nebulaId), &info) 91 | } 92 | 93 | func parseNebulaCustomParamsKey(value []byte) (account.NebulaId, error) { 94 | hex := []byte(strings.Split(string(value), Separator)[2]) 95 | key, err := hexutil.Decode(string(hex)) 96 | if err != nil { 97 | return [32]byte{}, err 98 | } 99 | var pubKey account.NebulaId 100 | copy(pubKey[:], key[:]) 101 | return pubKey, nil 102 | } 103 | func formNebulaCustomParamsKey(nebulaId account.NebulaId) []byte { 104 | return formKey(string(NebulaCustomParamsKey), hexutil.Encode(nebulaId[:])) 105 | } 106 | 107 | func (storage *Storage) NebulaCustomParams(nebulaId account.NebulaId) (*NebulaCustomParams, error) { 108 | b, err := storage.getValue(formNebulaCustomParamsKey(nebulaId)) 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | var nebulae NebulaCustomParams 114 | err = json.Unmarshal(b, &nebulae) 115 | if err != nil { 116 | return nil, err 117 | } 118 | 119 | return &nebulae, err 120 | } 121 | 122 | func (storage *Storage) DropNebulaCustomParams(nebulaId account.NebulaId) error { 123 | return storage.dropValue(formNebulaCustomParamsKey(nebulaId)) 124 | } 125 | 126 | func (storage *Storage) SetNebulaCustomParams(nebulaId account.NebulaId, customParams NebulaCustomParams) error { 127 | return storage.setValue(formNebulaCustomParamsKey(nebulaId), &customParams) 128 | } 129 | -------------------------------------------------------------------------------- /common/storage/oracles.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/Gravity-Tech/gravity-core/common/account" 9 | 10 | "github.com/ethereum/go-ethereum/common/hexutil" 11 | ) 12 | 13 | type OraclesByTypeMap map[account.ChainType]account.OraclesPubKey 14 | type OraclesMap map[string]account.ChainType 15 | 16 | func formBftOraclesByNebulaKey(nebulaId account.NebulaId) []byte { 17 | return formKey(string(BftOraclesByNebulaKey), hexutil.Encode(nebulaId[:])) 18 | } 19 | func formNebulaOraclesIndexKey(nebulaId account.NebulaId) []byte { 20 | return formKey(string(NebulaOraclesIndexKey), hexutil.Encode(nebulaId[:])) 21 | } 22 | func formSignOraclesByConsulKey(consulPubKey account.ConsulPubKey, nebulaId account.NebulaId, roundId int64) []byte { 23 | return formKey(string(SignOraclesResultByConsulKey), hexutil.Encode(consulPubKey[:]), hexutil.Encode(nebulaId[:]), fmt.Sprintf("%d", roundId)) 24 | } 25 | func formOraclesByConsulKey(consulPubKey account.ConsulPubKey) []byte { 26 | return formKey(string(OraclesByValidatorKey), hexutil.Encode(consulPubKey[:])) 27 | } 28 | func formOraclesByNebulaKey(nebulaId account.NebulaId) []byte { 29 | return formKey(string(OraclesByNebulaKey), hexutil.Encode(nebulaId[:])) 30 | } 31 | func formNebulaeByOracleKey(pubKey account.OraclesPubKey) []byte { 32 | return formKey(string(NebulaeByOracleKey), hexutil.Encode(pubKey[:])) 33 | } 34 | 35 | func (storage *Storage) OraclesByNebula(nebulaId account.NebulaId) (OraclesMap, error) { 36 | b, err := storage.getValue(formOraclesByNebulaKey(nebulaId)) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | var oraclesByNebula OraclesMap 42 | err = json.Unmarshal(b, &oraclesByNebula) 43 | if err != nil { 44 | return oraclesByNebula, err 45 | } 46 | 47 | return oraclesByNebula, err 48 | } 49 | func (storage *Storage) SetOraclesByNebula(nebulaAddress account.NebulaId, oracles OraclesMap) error { 50 | return storage.setValue(formOraclesByNebulaKey(nebulaAddress), oracles) 51 | } 52 | 53 | func (storage *Storage) NebulaeByOracle(pubKey account.OraclesPubKey) ([]account.NebulaId, error) { 54 | b, err := storage.getValue(formNebulaeByOracleKey(pubKey)) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | var nebulae []account.NebulaId 60 | err = json.Unmarshal(b, &nebulae) 61 | if err != nil { 62 | return nebulae, err 63 | } 64 | 65 | return nebulae, err 66 | } 67 | func (storage *Storage) SetNebulaeByOracle(pubKey account.OraclesPubKey, nebulae []account.NebulaId) error { 68 | return storage.setValue(formNebulaeByOracleKey(pubKey), nebulae) 69 | } 70 | 71 | func (storage *Storage) NebulaOraclesIndex(nebulaAddress account.NebulaId) (uint64, error) { 72 | b, err := storage.getValue(formNebulaOraclesIndexKey(nebulaAddress)) 73 | if err != nil { 74 | return 0, err 75 | } 76 | 77 | return binary.BigEndian.Uint64(b), nil 78 | } 79 | func (storage *Storage) SetNebulaOraclesIndex(nebulaAddress account.NebulaId, index uint64) error { 80 | var b [8]byte 81 | binary.BigEndian.PutUint64(b[:], index) 82 | err := storage.setValue(formNebulaOraclesIndexKey(nebulaAddress), b[:]) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | return err 88 | } 89 | 90 | func (storage *Storage) OraclesByConsul(pubKey account.ConsulPubKey) (OraclesByTypeMap, error) { 91 | b, err := storage.getValue(formOraclesByConsulKey(pubKey)) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | var oracles OraclesByTypeMap 97 | err = json.Unmarshal(b, &oracles) 98 | if err != nil { 99 | return oracles, err 100 | } 101 | 102 | return oracles, err 103 | } 104 | func (storage *Storage) SetOraclesByConsul(pubKey account.ConsulPubKey, oracles OraclesByTypeMap) error { 105 | return storage.setValue(formOraclesByConsulKey(pubKey), oracles) 106 | } 107 | 108 | func (storage *Storage) SignOraclesByConsul(pubKey account.ConsulPubKey, nebulaId account.NebulaId, roundId int64) ([]byte, error) { 109 | key := formSignOraclesByConsulKey(pubKey, nebulaId, roundId) 110 | item, err := storage.txn.Get(key) 111 | if err != nil { 112 | return nil, err 113 | } 114 | 115 | b, err := item.ValueCopy(nil) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | return b, err 121 | } 122 | func (storage *Storage) SetSignOracles(pubKey account.ConsulPubKey, nebulaId account.NebulaId, roundId int64, sign []byte) error { 123 | return storage.setValue(formSignOraclesByConsulKey(pubKey, nebulaId, roundId), sign) 124 | } 125 | 126 | func (storage *Storage) BftOraclesByNebula(nebulaId account.NebulaId) (OraclesMap, error) { 127 | b, err := storage.getValue(formBftOraclesByNebulaKey(nebulaId)) 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | var oraclesByNebula OraclesMap 133 | err = json.Unmarshal(b, &oraclesByNebula) 134 | if err != nil { 135 | return oraclesByNebula, err 136 | } 137 | 138 | return oraclesByNebula, err 139 | } 140 | func (storage *Storage) SetBftOraclesByNebula(nebulaId account.NebulaId, oracles OraclesMap) error { 141 | return storage.setValue(formBftOraclesByNebulaKey(nebulaId), oracles) 142 | } 143 | -------------------------------------------------------------------------------- /common/storage/result.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | 7 | "github.com/Gravity-Tech/gravity-core/common/account" 8 | "github.com/dgraph-io/badger" 9 | 10 | "github.com/ethereum/go-ethereum/common/hexutil" 11 | ) 12 | 13 | func formResultKey(nebulaId account.NebulaId, pulseId int64, oraclePubKey account.OraclesPubKey) []byte { 14 | return formKey(string(SignResultKey), hexutil.Encode(nebulaId[:]), fmt.Sprintf("%d", pulseId), hexutil.Encode(oraclePubKey[:])) 15 | } 16 | 17 | func (storage *Storage) Result(nebulaId account.NebulaId, pulseId int64, oraclePubKey account.OraclesPubKey) ([]byte, error) { 18 | b, err := storage.getValue(formResultKey(nebulaId, pulseId, oraclePubKey)) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | return b, err 24 | } 25 | func (storage *Storage) Results(nebulaId account.NebulaId, pulseId uint64) ([]string, error) { 26 | it := storage.txn.NewIterator(badger.DefaultIteratorOptions) 27 | defer it.Close() 28 | 29 | prefix := formKey(string(SignResultKey), hexutil.Encode(nebulaId[:]), fmt.Sprintf("%d", pulseId)) 30 | var values []string 31 | for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() { 32 | item := it.Item() 33 | item.Value(func(v []byte) error { 34 | values = append(values, base64.StdEncoding.EncodeToString(v)) 35 | return nil 36 | }) 37 | } 38 | 39 | return values, nil 40 | } 41 | func (storage *Storage) SetResult(nebulaId account.NebulaId, pulseId int64, oraclePubKey account.OraclesPubKey, sign []byte) error { 42 | return storage.setValue(formResultKey(nebulaId, pulseId, oraclePubKey), sign) 43 | } 44 | -------------------------------------------------------------------------------- /common/storage/reveal.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | 7 | "github.com/dgraph-io/badger" 8 | "go.uber.org/zap" 9 | 10 | "github.com/Gravity-Tech/gravity-core/common/account" 11 | 12 | "github.com/ethereum/go-ethereum/common/hexutil" 13 | ) 14 | 15 | func formRevealKey(nebulaId account.NebulaId, height int64, pulseId int64, commitHash []byte, oraclePubKey account.OraclesPubKey) []byte { 16 | return formKey(string(RevealKey), hexutil.Encode(nebulaId[:]), fmt.Sprintf("%d", height), fmt.Sprintf("%d", pulseId), hexutil.Encode(commitHash), hexutil.Encode(oraclePubKey[:])) 17 | } 18 | 19 | func (storage *Storage) Reveal(nebulaId account.NebulaId, height int64, pulseId int64, commitHash []byte, oraclePubKey account.OraclesPubKey) ([]byte, error) { 20 | zap.L().Sugar().Debugf("Reveal key: %s", formRevealKey(nebulaId, height, pulseId, commitHash, oraclePubKey)) 21 | b, err := storage.getValue(formRevealKey(nebulaId, height, pulseId, commitHash, oraclePubKey)) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | return b, err 27 | } 28 | 29 | func (storage *Storage) Reveals(nebulaId account.NebulaId, height int64, pulseId int64) ([]string, error) { 30 | it := storage.txn.NewIterator(badger.DefaultIteratorOptions) 31 | defer it.Close() 32 | 33 | prefix := formKey(string(RevealKey), hexutil.Encode(nebulaId[:]), fmt.Sprintf("%d", height), fmt.Sprintf("%d", pulseId)) 34 | zap.L().Sugar().Debugf("Reveals key prefix: %s", prefix) 35 | var values []string 36 | for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() { 37 | item := it.Item() 38 | item.Value(func(v []byte) error { 39 | values = append(values, base64.StdEncoding.EncodeToString(v)) 40 | return nil 41 | }) 42 | } 43 | 44 | return values, nil 45 | } 46 | 47 | func (storage *Storage) SetReveal(nebulaId account.NebulaId, height int64, pulseId int64, commitHash []byte, oraclePubKey account.OraclesPubKey, reveal []byte) error { 48 | zap.L().Sugar().Debugf("SetReveal key: %s", formRevealKey(nebulaId, height, pulseId, commitHash, oraclePubKey)) 49 | return storage.setValue(formRevealKey(nebulaId, height, pulseId, commitHash, oraclePubKey), reveal) 50 | } 51 | -------------------------------------------------------------------------------- /common/storage/round.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | 7 | "github.com/Gravity-Tech/gravity-core/common/account" 8 | ) 9 | 10 | func formNewRoundKey(chainType account.ChainType, ledgerHeight uint64) []byte { 11 | return formKey(string(BlockKey), chainType.String(), fmt.Sprintf("%d", ledgerHeight)) 12 | } 13 | 14 | func (storage *Storage) RoundHeight(chainType account.ChainType, ledgerHeight uint64) (uint64, error) { 15 | b, err := storage.getValue(formNewRoundKey(chainType, ledgerHeight)) 16 | if err != nil { 17 | return 0, err 18 | } 19 | 20 | return binary.BigEndian.Uint64(b), err 21 | } 22 | 23 | func (storage *Storage) SetNewRound(chainType account.ChainType, ledgerHeight uint64, tcHeight uint64) error { 24 | var b [8]byte 25 | binary.BigEndian.PutUint64(b[:], tcHeight) 26 | return storage.setValue(formNewRoundKey(chainType, ledgerHeight), b[:]) 27 | } 28 | -------------------------------------------------------------------------------- /common/storage/scores.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "encoding/binary" 5 | "strings" 6 | 7 | "github.com/dgraph-io/badger" 8 | 9 | "github.com/Gravity-Tech/gravity-core/common/account" 10 | "github.com/ethereum/go-ethereum/common/hexutil" 11 | ) 12 | 13 | type ScoresByConsulMap map[account.ConsulPubKey]uint64 14 | 15 | func formScoreKey(pubKey account.ConsulPubKey) []byte { 16 | return formKey(string(ScoreKey), hexutil.Encode(pubKey[:])) 17 | } 18 | func parseScoreKey(value []byte) (account.ConsulPubKey, error) { 19 | hex := []byte(strings.Split(string(value), Separator)[1]) 20 | key, err := hexutil.Decode(string(hex)) 21 | if err != nil { 22 | return [32]byte{}, err 23 | } 24 | var pubKey account.ConsulPubKey 25 | copy(pubKey[:], key[:]) 26 | return pubKey, nil 27 | } 28 | 29 | func (storage *Storage) Score(pubKey account.ConsulPubKey) (uint64, error) { 30 | b, err := storage.getValue(formScoreKey(pubKey)) 31 | if err != nil { 32 | return 0, err 33 | } 34 | 35 | return binary.BigEndian.Uint64(b), nil 36 | } 37 | func (storage *Storage) SetScore(pubKey account.ConsulPubKey, score uint64) error { 38 | var b [8]byte 39 | binary.BigEndian.PutUint64(b[:], score) 40 | err := storage.setValue(formScoreKey(pubKey), b[:]) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | return err 46 | } 47 | 48 | func (storage *Storage) Scores() (ScoresByConsulMap, error) { 49 | it := storage.txn.NewIterator(badger.DefaultIteratorOptions) 50 | defer it.Close() 51 | 52 | prefix := []byte(ScoreKey) 53 | scores := make(ScoresByConsulMap) 54 | for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() { 55 | item := it.Item() 56 | k := item.Key() 57 | item.Value(func(v []byte) error { 58 | pubKey, err := parseScoreKey(k) 59 | if err != nil { 60 | return err 61 | } 62 | scores[pubKey] = binary.BigEndian.Uint64(v) 63 | return nil 64 | }) 65 | } 66 | 67 | return scores, nil 68 | } 69 | -------------------------------------------------------------------------------- /common/storage/solana.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import "fmt" 4 | 5 | func (storage *Storage) SetSolanaRecentBlock(round int, blockHash []byte) error { 6 | return storage.setValue([]byte(fmt.Sprintf("solana_recent_block_%d", round)), blockHash) 7 | } 8 | -------------------------------------------------------------------------------- /common/storage/storage.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | 7 | "github.com/dgraph-io/badger" 8 | ) 9 | 10 | const ( 11 | Separator string = "_" 12 | 13 | ConsulsKey Key = "consuls" 14 | ConsulsCandidateKey Key = "consuls_candidate" 15 | LastHeightKey Key = "last_height" 16 | LastRoundApproved Key = "last_round_approved" 17 | ConsulsCountKey Key = "consuls_count" 18 | SignConsulsResultByConsulKey Key = "consuls_sing" 19 | SignOraclesResultByConsulKey Key = "oracles_sign" 20 | NebulaeByOracleKey Key = "nebulae_by_oracle" 21 | NebulaOraclesIndexKey Key = "nebula_oracles_index" 22 | OraclesByNebulaKey Key = "oracles_by_nebula" 23 | BftOraclesByNebulaKey Key = "bft_oracles_nebula" 24 | OraclesByValidatorKey Key = "oracles" 25 | 26 | BlockKey Key = "block" 27 | VoteKey Key = "vote" 28 | ScoreKey Key = "score" 29 | CommitKey Key = "commit" 30 | RevealKey Key = "reveal" 31 | SignResultKey Key = "signResult" 32 | NebulaInfoKey Key = "nebula_info" 33 | NebulaCustomParamsKey Key = "nebula_custom_params" 34 | ) 35 | 36 | var ( 37 | ErrKeyNotFound = badger.ErrKeyNotFound 38 | ) 39 | 40 | type Key string 41 | type Storage struct { 42 | txn *badger.Txn 43 | } 44 | 45 | func formKey(args ...string) []byte { 46 | return []byte(strings.Join(args, Separator)) 47 | } 48 | 49 | func New() *Storage { 50 | return &Storage{} 51 | } 52 | 53 | func (storage *Storage) getValue(key []byte) ([]byte, error) { 54 | item, err := storage.txn.Get(key) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | b, err := item.ValueCopy(nil) 60 | if err != nil { 61 | return nil, err 62 | 63 | } 64 | 65 | return b, nil 66 | } 67 | 68 | func (storage *Storage) setValue(key []byte, jsonOrBytes interface{}) error { 69 | var b []byte 70 | var err error 71 | 72 | b, ok := jsonOrBytes.([]byte) 73 | if !ok { 74 | b, err = json.Marshal(jsonOrBytes) 75 | if err != nil { 76 | return err 77 | } 78 | } 79 | 80 | return storage.txn.Set(key, b) 81 | } 82 | 83 | func (storage *Storage) dropValue(key []byte) error { 84 | return storage.txn.Delete(key) 85 | } 86 | 87 | func (storage *Storage) NewTransaction(db *badger.DB) { 88 | storage.txn = db.NewTransaction(true) 89 | } 90 | 91 | func (storage *Storage) Commit() error { 92 | return storage.txn.Commit() 93 | } 94 | -------------------------------------------------------------------------------- /common/storage/system.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import "encoding/binary" 4 | 5 | func (storage *Storage) LastHeight() (uint64, error) { 6 | b, err := storage.getValue([]byte(LastHeightKey)) 7 | if err != nil { 8 | return 0, err 9 | } 10 | 11 | return binary.BigEndian.Uint64(b), nil 12 | } 13 | 14 | func (storage *Storage) SetLastHeight(height uint64) error { 15 | var b [8]byte 16 | binary.BigEndian.PutUint64(b[:], height) 17 | err := storage.setValue([]byte(LastHeightKey), b[:]) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | return err 23 | } 24 | 25 | func (storage *Storage) ConsulsCount() (int, error) { 26 | b, err := storage.getValue([]byte(ConsulsCountKey)) 27 | if err != nil { 28 | return 0, err 29 | } 30 | 31 | return int(binary.BigEndian.Uint64(b)), nil 32 | } 33 | 34 | func (storage *Storage) SetConsulsCount(consulsCount int) error { 35 | var b [8]byte 36 | binary.BigEndian.PutUint64(b[:], uint64(consulsCount)) 37 | err := storage.setValue([]byte(ConsulsCountKey), b[:]) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | return err 43 | } 44 | 45 | func (storage *Storage) SetLastRoundApproved(roundId uint64) error { 46 | var b [8]byte 47 | binary.BigEndian.PutUint64(b[:], roundId) 48 | err := storage.setValue([]byte(LastRoundApproved), b[:]) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | return err 54 | } 55 | func (storage *Storage) LastRoundApproved() (uint64, error) { 56 | b, err := storage.getValue([]byte(LastRoundApproved)) 57 | if err != nil { 58 | return 0, err 59 | } 60 | 61 | return binary.BigEndian.Uint64(b), nil 62 | } 63 | -------------------------------------------------------------------------------- /common/storage/vote.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | 7 | "github.com/dgraph-io/badger" 8 | 9 | "github.com/ethereum/go-ethereum/common/hexutil" 10 | 11 | "github.com/Gravity-Tech/gravity-core/common/account" 12 | ) 13 | 14 | type Vote struct { 15 | PubKey account.ConsulPubKey 16 | Score uint64 17 | } 18 | 19 | type VoteByConsulMap map[account.ConsulPubKey][]Vote 20 | 21 | func formVoteKey(pubKey account.ConsulPubKey) []byte { 22 | return formKey(string(VoteKey), hexutil.Encode(pubKey[:])) 23 | } 24 | func parseVoteKey(value []byte) (account.ConsulPubKey, error) { 25 | hex := []byte(strings.Split(string(value), Separator)[1]) 26 | key, err := hexutil.Decode(string(hex)) 27 | if err != nil { 28 | return [32]byte{}, err 29 | } 30 | var pubKey account.ConsulPubKey 31 | copy(pubKey[:], key[:]) 32 | return pubKey, nil 33 | } 34 | 35 | func (storage *Storage) Vote(pubKey account.ConsulPubKey) ([]Vote, error) { 36 | b, err := storage.getValue(formVoteKey(pubKey)) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | var votes []Vote 42 | err = json.Unmarshal(b, &votes) 43 | if err != nil { 44 | return nil, err 45 | } 46 | return votes, err 47 | } 48 | func (storage *Storage) SetVote(pubKey account.ConsulPubKey, votes []Vote) error { 49 | return storage.setValue(formVoteKey(pubKey), votes) 50 | } 51 | 52 | func (storage *Storage) Votes() (VoteByConsulMap, error) { 53 | it := storage.txn.NewIterator(badger.DefaultIteratorOptions) 54 | defer it.Close() 55 | 56 | prefix := []byte(VoteKey) 57 | votes := make(VoteByConsulMap) 58 | for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() { 59 | item := it.Item() 60 | k := item.Key() 61 | item.Value(func(v []byte) error { 62 | var vote []Vote 63 | err := json.Unmarshal(v, &vote) 64 | if err != nil { 65 | return err 66 | } 67 | pubKey, err := parseVoteKey(k) 68 | if err != nil { 69 | return err 70 | } 71 | votes[pubKey] = vote 72 | return nil 73 | }) 74 | } 75 | 76 | return votes, nil 77 | } 78 | -------------------------------------------------------------------------------- /common/transactions/transactions.go: -------------------------------------------------------------------------------- 1 | package transactions 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | "time" 7 | 8 | "github.com/Gravity-Tech/gravity-core/common/account" 9 | "github.com/ethereum/go-ethereum/crypto" 10 | tCrypto "github.com/tendermint/tendermint/crypto" 11 | _ "github.com/tendermint/tendermint/crypto/ed25519" 12 | ) 13 | 14 | const ( 15 | Commit TxFunc = "commit" 16 | Reveal TxFunc = "reveal" 17 | AddOracle TxFunc = "addOracle" 18 | AddOracleInNebula TxFunc = "addOracleInNebula" 19 | Result TxFunc = "result" 20 | NewRound TxFunc = "newRound" //TODO: Legacy / not used 21 | Vote TxFunc = "vote" 22 | AddNebula TxFunc = "setNebula" 23 | DropNebula TxFunc = "dropNebula" 24 | SignNewConsuls TxFunc = "signNewConsuls" 25 | SignNewOracles TxFunc = "signNewOracles" 26 | ApproveLastRound TxFunc = "approveLastRound" 27 | SetSolanaRecentBlock TxFunc = "setSolanaRecentBlock" 28 | SetNebulaCustomParams TxFunc = "setNebulaCustomParams" 29 | DropNebulaCustomParams TxFunc = "dropNebulaCustomParams" 30 | 31 | String Type = "string" 32 | Int Type = "int" 33 | Bytes Type = "bytes" 34 | ) 35 | 36 | type ID [32]byte 37 | type TxFunc string 38 | type Type string 39 | type Arg struct { 40 | Type Type 41 | Value []byte 42 | } 43 | type Value interface{} 44 | type StringValue struct { 45 | Value string 46 | } 47 | type IntValue struct { 48 | Value int64 49 | } 50 | type BytesValue struct { 51 | Value []byte 52 | } 53 | 54 | type Transaction struct { 55 | Id ID 56 | SenderPubKey account.ConsulPubKey 57 | Signature [72]byte 58 | Func TxFunc 59 | Timestamp uint64 60 | Args []Arg 61 | } 62 | 63 | func New(pubKey account.ConsulPubKey, funcName TxFunc, privKey tCrypto.PrivKey) (*Transaction, error) { 64 | tx := &Transaction{ 65 | SenderPubKey: pubKey, 66 | Func: funcName, 67 | Timestamp: uint64(time.Now().Unix()), 68 | } 69 | tx.Hash() 70 | 71 | err := tx.Sign(privKey) 72 | if err != nil { 73 | return tx, err 74 | } 75 | 76 | return tx, nil 77 | } 78 | 79 | func (tx *Transaction) Hash() { 80 | tx.Id = ID(crypto.Keccak256Hash(tx.Bytes())) 81 | } 82 | 83 | func (tx *Transaction) Sign(privKey tCrypto.PrivKey) error { 84 | sign, err := account.Sign(privKey, tx.Id.Bytes()) 85 | if err != nil { 86 | return err 87 | } 88 | 89 | copy(tx.Signature[:], sign) 90 | return nil 91 | } 92 | 93 | func (tx *Transaction) Bytes() []byte { 94 | var result []byte 95 | result = append(result, tx.Id[:]...) 96 | result = append(result, tx.SenderPubKey[:]...) 97 | result = append(result, tx.Func...) 98 | 99 | for _, v := range tx.Args { 100 | result = append(result, v.Value...) 101 | } 102 | 103 | var b [8]byte 104 | binary.BigEndian.PutUint64(b[:], tx.Timestamp) 105 | result = append(result, b[:]...) 106 | 107 | return result 108 | } 109 | 110 | func UnmarshalJson(data []byte) (*Transaction, error) { 111 | tx := new(Transaction) 112 | err := json.Unmarshal(data, tx) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | return tx, nil 118 | } 119 | 120 | func (id ID) Bytes() []byte { 121 | return id[:] 122 | } 123 | 124 | func (tx *Transaction) AddValue(value Value) { 125 | var b []byte 126 | var t Type 127 | switch value.(type) { 128 | case StringValue: 129 | b = []byte(value.(StringValue).Value) 130 | t = String 131 | case IntValue: 132 | var bInt [8]byte 133 | binary.BigEndian.PutUint64(bInt[:], uint64(value.(IntValue).Value)) 134 | b = bInt[:] 135 | t = Int 136 | case BytesValue: 137 | b = value.(BytesValue).Value 138 | t = Bytes 139 | } 140 | tx.Args = append(tx.Args, Arg{ 141 | Type: t, 142 | Value: b, 143 | }) 144 | } 145 | 146 | func (tx *Transaction) AddValues(values []Value) { 147 | for _, value := range values { 148 | tx.AddValue(value) 149 | } 150 | } 151 | 152 | func (tx *Transaction) Value(index int) interface{} { 153 | v := tx.Args[index] 154 | 155 | switch v.Type { 156 | case String: 157 | return string(v.Value) 158 | case Int: 159 | return int64(binary.BigEndian.Uint64(v.Value)) 160 | case Bytes: 161 | return v.Value 162 | } 163 | 164 | return nil 165 | } 166 | -------------------------------------------------------------------------------- /config/addrbook.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "61194e84290e998ac46be727", 3 | "addrs": [] 4 | } -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | 7 | "github.com/Gravity-Tech/gravity-core/common/account" 8 | 9 | "github.com/ethereum/go-ethereum/common/hexutil" 10 | 11 | ethCrypto "github.com/ethereum/go-ethereum/crypto" 12 | 13 | "github.com/tendermint/tendermint/crypto/ed25519" 14 | wavesplatform "github.com/wavesplatform/go-lib-crypto" 15 | ) 16 | 17 | type Keys struct { 18 | Validator Key 19 | TargetChains map[string]Key 20 | } 21 | 22 | type Key struct { 23 | Address string 24 | PubKey string 25 | PrivKey string 26 | } 27 | 28 | func generateEthereumBasedPrivKeys() (*Key, error) { 29 | ethPrivKey, err := ethCrypto.GenerateKey() 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | return &Key{ 35 | Address: ethCrypto.PubkeyToAddress(ethPrivKey.PublicKey).String(), 36 | PubKey: hexutil.Encode(ethCrypto.CompressPubkey(ðPrivKey.PublicKey)), 37 | PrivKey: hexutil.Encode(ethCrypto.FromECDSA(ethPrivKey)), 38 | }, nil 39 | } 40 | 41 | func generateWavesPrivKeys(chain byte) (*Key, error) { 42 | wCrypto := wavesplatform.NewWavesCrypto() 43 | wSeed := wCrypto.RandomSeed() 44 | 45 | return &Key{ 46 | Address: string(wCrypto.AddressFromSeed(wSeed, wavesplatform.WavesChainID(chain))), 47 | PubKey: string(wCrypto.PublicKey(wSeed)), 48 | PrivKey: string(wSeed), 49 | }, nil 50 | } 51 | 52 | func GeneratePrivKeys(wavesChainID byte) (*Keys, error) { 53 | validatorPrivKey := ed25519.GenPrivKey() 54 | 55 | ethPrivKeys, err := generateEthereumBasedPrivKeys() 56 | if err != nil { 57 | return nil, err 58 | } 59 | wavesPrivKeys, err := generateWavesPrivKeys(wavesChainID) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | return &Keys{ 65 | Validator: Key{ 66 | Address: hexutil.Encode(validatorPrivKey.PubKey().Bytes()[5:]), 67 | PubKey: hexutil.Encode(validatorPrivKey.PubKey().Bytes()[5:]), 68 | PrivKey: hexutil.Encode(validatorPrivKey[:]), 69 | }, 70 | TargetChains: map[string]Key{ 71 | account.Ethereum.String(): *ethPrivKeys, 72 | account.Binance.String(): *ethPrivKeys, 73 | account.Waves.String(): *wavesPrivKeys, 74 | account.Avax.String(): *ethPrivKeys, 75 | account.Heco.String(): *ethPrivKeys, 76 | account.Fantom.String(): *ethPrivKeys, 77 | account.XDai.String(): *ethPrivKeys, 78 | account.Okex.String(): *ethPrivKeys, 79 | }, 80 | }, nil 81 | } 82 | func ParseConfig(filename string, config interface{}) error { 83 | file, err := ioutil.ReadFile(filename) 84 | if err != nil { 85 | return err 86 | } 87 | if err := json.Unmarshal(file, config); err != nil { 88 | return err 89 | } 90 | return nil 91 | } 92 | -------------------------------------------------------------------------------- /config/genesis.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/tendermint/tendermint/types" 7 | ) 8 | 9 | type Genesis struct { 10 | ConsulsCount int 11 | GenesisTime time.Time 12 | ChainID string 13 | Block types.BlockParams 14 | Evidence types.EvidenceParams 15 | InitScore map[string]uint64 16 | OraclesAddressByValidator map[string]map[string]string 17 | } 18 | -------------------------------------------------------------------------------- /config/ledger.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/Gravity-Tech/gravity-core/common/account" 7 | cfg "github.com/tendermint/tendermint/config" 8 | ) 9 | 10 | const ( 11 | DefaultMoniker = "robot" 12 | ) 13 | 14 | type AdaptorsConfig struct { 15 | NodeUrl string 16 | ChainId string 17 | ChainType string 18 | GravityContractAddress string 19 | Custom map[string]interface{} `json:"custom,optional"` 20 | } 21 | 22 | type ValidatorDetails struct { 23 | Name, Description, JoinedAt string 24 | // Misc 25 | AvatarURL, Website string 26 | } 27 | 28 | func (validatorDetails *ValidatorDetails) Bytes() ([]byte, error) { 29 | res, err := json.Marshal(validatorDetails) 30 | 31 | if err != nil { 32 | return make([]byte, 0), err 33 | } 34 | 35 | return res, nil 36 | } 37 | 38 | func (validatorDetails *ValidatorDetails) DefaultNew() *ValidatorDetails { 39 | return &ValidatorDetails{ 40 | Name: "Gravity Node", Description: "", JoinedAt: "", 41 | AvatarURL: "", Website: "", 42 | } 43 | } 44 | 45 | type LedgerConfig struct { 46 | Moniker string 47 | IsFastSync bool 48 | Mempool *cfg.MempoolConfig 49 | RPC *cfg.RPCConfig 50 | P2P *cfg.P2PConfig 51 | 52 | Details *ValidatorDetails 53 | PublicIP string 54 | 55 | Adapters map[string]AdaptorsConfig 56 | } 57 | 58 | func DefaultLedgerConfig() LedgerConfig { 59 | return LedgerConfig{ 60 | Moniker: DefaultMoniker, 61 | IsFastSync: true, 62 | Mempool: cfg.DefaultMempoolConfig(), 63 | RPC: cfg.DefaultRPCConfig(), 64 | P2P: cfg.DefaultP2PConfig(), 65 | Details: (&ValidatorDetails{}).DefaultNew(), 66 | Adapters: map[string]AdaptorsConfig{ 67 | account.Ethereum.String(): { 68 | NodeUrl: "", 69 | GravityContractAddress: "", 70 | }, 71 | account.Waves.String(): { 72 | NodeUrl: "", 73 | GravityContractAddress: "", 74 | }, 75 | }, 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /config/oracle.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type OracleConfig struct { 4 | TargetChainNodeUrl string 5 | ChainId string 6 | GravityNodeUrl string 7 | ChainType string 8 | ExtractorUrl string 9 | BlocksInterval uint64 10 | Custom map[string]interface{} 11 | } 12 | -------------------------------------------------------------------------------- /contracts/ethereum/Gravity/Gravity.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <=0.7.0; 2 | 3 | contract Gravity { 4 | mapping(uint256=>address[]) public rounds; 5 | uint256 public bftValue; 6 | uint256 public lastRound; 7 | 8 | constructor(address[] memory consuls, uint256 newBftValue) public { 9 | rounds[0] = consuls; 10 | bftValue = newBftValue; 11 | } 12 | 13 | function getConsuls() external view returns(address[] memory) { 14 | return rounds[lastRound]; 15 | } 16 | 17 | function getConsulsByRoundId(uint256 roundId) external view returns(address[] memory) { 18 | return rounds[roundId]; 19 | } 20 | 21 | function updateConsuls(address[] memory newConsuls, uint8[] memory v, bytes32[] memory r, bytes32[] memory s, uint256 roundId) public { 22 | uint256 count = 0; 23 | 24 | require(roundId > lastRound, "round less last round"); 25 | 26 | bytes32 dataHash = hashNewConsuls(newConsuls, roundId); 27 | 28 | address[] memory consuls = rounds[lastRound]; 29 | for(uint i = 0; i < consuls.length; i++) { 30 | count += ecrecover(dataHash, v[i], r[i], s[i]) == consuls[i] ? 1 : 0; 31 | } 32 | require(count >= bftValue, "invalid bft count"); 33 | 34 | rounds[roundId] = newConsuls; 35 | lastRound = roundId; 36 | } 37 | 38 | function hashNewConsuls(address[] memory newConsuls, uint256 roundId) public pure returns(bytes32) { 39 | bytes memory data; 40 | for(uint i = 0; i < newConsuls.length; i++) { 41 | data = abi.encodePacked(data, newConsuls[i]); 42 | } 43 | 44 | 45 | return keccak256(abi.encodePacked(data, roundId)); 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /contracts/ethereum/Gravity/Models.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <=0.7.0; 2 | 3 | library Models { 4 | struct Score { 5 | address owner; 6 | uint256 score; 7 | } 8 | } -------------------------------------------------------------------------------- /contracts/ethereum/Mock/SubMockBytes.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <=0.7.0; 2 | 3 | import "../interfaces/ISubscriberBytes.sol"; 4 | 5 | contract SubMockBytes is ISubscriberBytes { 6 | address payable nebulaAddress; 7 | uint256 reward; 8 | bool public isSent; 9 | constructor(address payable newNebulaAddress, uint256 newReward) public { 10 | nebulaAddress = newNebulaAddress; 11 | reward = newReward; 12 | } 13 | 14 | receive() external payable { } 15 | 16 | function attachValue(bytes calldata data) override external { 17 | isSent = true; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /contracts/ethereum/Mock/SubMockInt.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <=0.7.0; 2 | 3 | import "../interfaces/ISubscriberInt.sol"; 4 | 5 | contract SubMockInt is ISubscriberInt { 6 | address payable nebulaAddress; 7 | uint256 reward; 8 | bool public isSent; 9 | constructor(address payable newNebulaAddress, uint256 newReward) public { 10 | nebulaAddress = newNebulaAddress; 11 | reward = newReward; 12 | } 13 | 14 | receive() external payable { } 15 | 16 | function attachValue(int64 data) override external { 17 | isSent = true; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /contracts/ethereum/Mock/SubMockString.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <=0.7.0; 2 | 3 | import "../interfaces/ISubscriberString.sol"; 4 | 5 | contract SubMockString is ISubscriberString { 6 | address payable nebulaAddress; 7 | uint256 reward; 8 | bool public isSent; 9 | constructor(address payable newNebulaAddress, uint256 newReward) public { 10 | nebulaAddress = newNebulaAddress; 11 | reward = newReward; 12 | } 13 | 14 | receive() external payable { } 15 | 16 | function attachValue(string calldata data) override external { 17 | isSent = true; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /contracts/ethereum/Nebula/NModels.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <=0.7.0; 2 | 3 | library NModels { 4 | uint8 constant oracleCountInEpoch = 5; 5 | 6 | enum DataType { 7 | Int64, 8 | String, 9 | Bytes 10 | } 11 | 12 | struct Subscription { 13 | address owner; 14 | address payable contractAddress; 15 | uint8 minConfirmations; 16 | uint256 reward; 17 | } 18 | 19 | struct Pulse { 20 | bytes32 dataHash; 21 | uint256 height; 22 | } 23 | 24 | struct Oracle { 25 | address owner; 26 | bool isOnline; 27 | bytes32 idInQueue; 28 | } 29 | } -------------------------------------------------------------------------------- /contracts/ethereum/Nebula/Nebula.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <=0.7.0; 2 | 3 | import "../Gravity/Gravity.sol"; 4 | import "../libs/Queue.sol"; 5 | import "./NModels.sol"; 6 | import "../interfaces/ISubscriberBytes.sol"; 7 | import "../interfaces/ISubscriberInt.sol"; 8 | import "../interfaces/ISubscriberString.sol"; 9 | 10 | 11 | contract Nebula { 12 | event NewPulse(uint256 pulseId, uint256 height, bytes32 dataHash); 13 | event NewSubscriber(bytes32 id); 14 | 15 | mapping(uint256=>bool) public rounds; 16 | 17 | QueueLib.Queue public oracleQueue; 18 | QueueLib.Queue public subscriptionsQueue; 19 | QueueLib.Queue public pulseQueue; 20 | 21 | address[] public oracles; 22 | uint256 public bftValue; 23 | address public gravityContract; 24 | NModels.DataType public dataType; 25 | 26 | bytes32[] public subscriptionIds; 27 | uint256 public lastPulseId; 28 | mapping(bytes32 => NModels.Subscription) public subscriptions; 29 | mapping(uint256 => NModels.Pulse) public pulses; 30 | mapping(uint256 => mapping(bytes32 => bool)) public isPulseSubSent; 31 | 32 | constructor(NModels.DataType newDataType, address newGravityContract, address[] memory newOracle, uint256 newBftValue) public { 33 | dataType = newDataType; 34 | oracles = newOracle; 35 | bftValue = newBftValue; 36 | gravityContract = newGravityContract; 37 | } 38 | 39 | receive() external payable { } 40 | 41 | //----------------------------------public getters-------------------------------------------------------------- 42 | 43 | function getOracles() public view returns(address[] memory) { 44 | return oracles; 45 | } 46 | 47 | function getSubscribersIds() public view returns(bytes32[] memory) { 48 | return subscriptionIds; 49 | } 50 | 51 | function hashNewOracles(address[] memory newOracles) public pure returns(bytes32) { 52 | bytes memory data; 53 | for(uint i = 0; i < newOracles.length; i++) { 54 | data = abi.encodePacked(data, newOracles[i]); 55 | } 56 | 57 | return keccak256(data); 58 | } 59 | 60 | //----------------------------------public setters-------------------------------------------------------------- 61 | 62 | function sendHashValue(bytes32 dataHash, uint8[] memory v, bytes32[] memory r, bytes32[] memory s) public { 63 | uint256 count = 0; 64 | 65 | for(uint i = 0; i < oracles.length; i++) { 66 | count += ecrecover(dataHash, 67 | v[i], r[i], s[i]) == oracles[i] ? 1 : 0; 68 | } 69 | 70 | require(count >= bftValue, "invalid bft count"); 71 | 72 | uint256 newPulseId = lastPulseId + 1; 73 | pulses[newPulseId] = NModels.Pulse(dataHash, block.number); 74 | 75 | emit NewPulse(newPulseId, block.number, dataHash); 76 | lastPulseId = newPulseId; 77 | } 78 | 79 | function updateOracles(address[] memory newOracles, uint8[] memory v, bytes32[] memory r, bytes32[] memory s, uint256 newRound) public { 80 | uint256 count = 0; 81 | bytes32 dataHash = hashNewOracles(newOracles); 82 | address[] memory consuls = Gravity(gravityContract).getConsuls(); 83 | 84 | for(uint i = 0; i < consuls.length; i++) { 85 | count += ecrecover(dataHash, v[i], r[i], s[i]) == consuls[i] ? 1 : 0; 86 | } 87 | require(count >= bftValue, "invalid bft count"); 88 | 89 | oracles = newOracles; 90 | rounds[newRound] = true; 91 | } 92 | 93 | function sendValueToSubByte(bytes memory value, uint256 pulseId, bytes32 subId) public { 94 | require(keccak256(abi.encodePacked(value)) == pulses[pulseId].dataHash, "value was not approved by oracles"); 95 | sendValueToSub(pulseId, subId); 96 | ISubscriberBytes(subscriptions[subId].contractAddress).attachValue(value); 97 | } 98 | 99 | function sendValueToSubInt(int64 value, uint256 pulseId, bytes32 subId) public { 100 | require(keccak256(abi.encodePacked(value)) == pulses[pulseId].dataHash, "value was not approved by oracles"); 101 | sendValueToSub(pulseId, subId); 102 | ISubscriberInt(subscriptions[subId].contractAddress).attachValue(value); 103 | } 104 | 105 | function sendValueToSubString(string memory value, uint256 pulseId, bytes32 subId) public { 106 | require(keccak256(abi.encodePacked(value)) == pulses[pulseId].dataHash, "value was not approved by oracles"); 107 | sendValueToSub(pulseId, subId); 108 | ISubscriberString(subscriptions[subId].contractAddress).attachValue(value); 109 | } 110 | 111 | //----------------------------------internals--------------------------------------------------------------------- 112 | 113 | function sendValueToSub(uint256 pulseId, bytes32 subId) internal { 114 | require(isPulseSubSent[pulseId][subId] == false, "sub sent"); 115 | 116 | isPulseSubSent[pulseId][subId] = true; 117 | } 118 | 119 | function subscribe(address payable contractAddress, uint8 minConfirmations, uint256 reward) public { 120 | bytes32 id = keccak256(abi.encodePacked(abi.encodePacked(msg.sig, msg.sender, contractAddress, minConfirmations))); 121 | require(subscriptions[id].owner == address(0x00), "rq exists"); 122 | subscriptions[id] = NModels.Subscription(msg.sender, contractAddress, minConfirmations, reward); 123 | QueueLib.push(subscriptionsQueue, id); 124 | subscriptionIds.push(id); 125 | emit NewSubscriber(id); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /contracts/ethereum/Nebula/TestNebula.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <=0.7.0; 2 | 3 | import "./Nebula.sol"; 4 | 5 | contract TestNebula is Nebula { 6 | 7 | constructor( 8 | NModels.DataType newDataType, 9 | address newGravityContract, 10 | address[] memory newOracle, 11 | uint256 newBftValue 12 | ) Nebula(newDataType, newGravityContract, newOracle, newBftValue) public {} 13 | 14 | function oraclePrevElement(bytes32 b) view external returns (bytes32) { 15 | return oracleQueue.prevElement[b]; 16 | } 17 | 18 | function oracleNextElement(bytes32 b) view external returns (bytes32) { 19 | return oracleQueue.nextElement[b]; 20 | } 21 | 22 | function subscriptionsPrevElement(bytes32 b) view external returns (bytes32) { 23 | return subscriptionsQueue.prevElement[b]; 24 | } 25 | 26 | function subscriptionsNextElement(bytes32 b) view external returns (bytes32) { 27 | return subscriptionsQueue.nextElement[b]; 28 | } 29 | 30 | function pulsePrevElement(bytes32 b) view external returns (bytes32) { 31 | return pulseQueue.prevElement[b]; 32 | } 33 | 34 | function pulseNextElement(bytes32 b) view external returns (bytes32) { 35 | return pulseQueue.nextElement[b]; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /contracts/ethereum/interfaces/ISubscriberBytes.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <=0.7.0; 2 | 3 | interface ISubscriberBytes { 4 | function attachValue(bytes calldata value) external; 5 | } -------------------------------------------------------------------------------- /contracts/ethereum/interfaces/ISubscriberInt.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <=0.7.0; 2 | 3 | interface ISubscriberInt { 4 | function attachValue(int64 value) external; 5 | } -------------------------------------------------------------------------------- /contracts/ethereum/interfaces/ISubscriberString.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <=0.7.0; 2 | 3 | interface ISubscriberString { 4 | function attachValue(string calldata value) external; 5 | } -------------------------------------------------------------------------------- /contracts/ethereum/interfaces/ISubscription.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <=0.7.0; 2 | 3 | interface ISubscription { 4 | function attachData(uint64 data) external; 5 | } -------------------------------------------------------------------------------- /contracts/ethereum/libs/Queue.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <=0.7; 2 | 3 | library QueueLib { 4 | struct Queue { 5 | bytes32 first; 6 | bytes32 last; 7 | mapping(bytes32 => bytes32) nextElement; 8 | mapping(bytes32 => bytes32) prevElement; 9 | } 10 | 11 | function drop(Queue storage queue, bytes32 rqHash) public { 12 | bytes32 prevElement = queue.prevElement[rqHash]; 13 | bytes32 nextElement = queue.nextElement[rqHash]; 14 | 15 | if (prevElement != bytes32(0)) { 16 | queue.nextElement[prevElement] = nextElement; 17 | } else { 18 | queue.first = nextElement; 19 | } 20 | 21 | if (nextElement != bytes32(0)) { 22 | queue.prevElement[nextElement] = prevElement; 23 | } else { 24 | queue.last = prevElement; 25 | } 26 | } 27 | 28 | // function next(Queue storage queue, bytes32 startRqHash) public view returns(bytes32) { 29 | // if (startRqHash == 0x000) 30 | // return queue.first; 31 | // else { 32 | // return queue.nextElement[startRqHash]; 33 | // } 34 | // } 35 | 36 | function push(Queue storage queue, bytes32 elementHash) public { 37 | if (queue.first == 0x000) { 38 | queue.first = elementHash; 39 | queue.last = elementHash; 40 | } else { 41 | queue.nextElement[queue.last] = elementHash; 42 | queue.prevElement[elementHash] = queue.last; 43 | queue.nextElement[elementHash] = bytes32(0); 44 | queue.last = elementHash; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/waves/gravity.ride: -------------------------------------------------------------------------------- 1 | {-# STDLIB_VERSION 3 #-} 2 | {-# CONTENT_TYPE DAPP #-} 3 | {-# SCRIPT_TYPE ACCOUNT #-} 4 | 5 | #-------------------Constants.....---------------------- 6 | let MaxConsuls = 5 7 | 8 | #-------------------Base functions---------------------- 9 | func getNumberByKey(key: String) = { 10 | match (getInteger(this, key)) { 11 | case v:Int => v 12 | case _ => 0 13 | } 14 | } 15 | func getStringByKey(key: String) = { 16 | match (getString(this, key)) { 17 | case v:String => v 18 | case _ => "" 19 | } 20 | } 21 | func getBooleanByKey(key: String) = { 22 | match (getBoolean(this, key)) { 23 | case v:Boolean => v 24 | case _ => false 25 | } 26 | } 27 | #-----------------------Key----------------------------- 28 | let BftCoefficientKey = "bft_coefficient" 29 | let LastRoundKey = "last_round" 30 | 31 | func ConsulsKey(round: Int) = "consuls_" + toString(round) 32 | #-------------------Global vars------------------------- 33 | let bftCoefficient = getNumberByKey(BftCoefficientKey) 34 | let lastRound = getNumberByKey(LastRoundKey) 35 | 36 | func consulsByRound(round: Int) = getStringByKey(ConsulsKey(round)).split(",") 37 | 38 | func validateSign(hash: ByteVector, sign: String, oracle: String) = { 39 | if (sign != "nil") then 40 | (if sigVerify(hash, fromBase58String(sign), fromBase58String(oracle)) then 1 else 0) 41 | else 0 42 | } 43 | 44 | #-------------------Callable---------------------------- 45 | @Callable(i) 46 | func updateConsuls(newConsuls: String, stringSigns: String, round: Int) = { 47 | let consuls = consulsByRound(lastRound) 48 | let msg = toBytes(newConsuls + "," + toString(round)) 49 | let signs = stringSigns.split(",") 50 | let count = validateSign(msg, signs[0], consuls[0]) + 51 | validateSign(msg, signs[1], consuls[1]) + 52 | validateSign(msg, signs[2], consuls[2]) + 53 | validateSign(msg, signs[3], consuls[3]) + 54 | validateSign(msg, signs[4], consuls[4]) 55 | 56 | if(round <= lastRound) 57 | then throw("round less last round") 58 | else if(count < bftCoefficient) 59 | then throw("invalid bft count") 60 | else { 61 | WriteSet([ 62 | DataEntry(ConsulsKey(round), newConsuls), 63 | DataEntry(LastRoundKey, round) 64 | ]) 65 | } 66 | } -------------------------------------------------------------------------------- /contracts/waves/nebula.ride: -------------------------------------------------------------------------------- 1 | {-# STDLIB_VERSION 3 #-} 2 | {-# CONTENT_TYPE DAPP #-} 3 | {-# SCRIPT_TYPE ACCOUNT #-} 4 | 5 | #-------------------Constants--------------------------- 6 | let WAVES = "WAVES" 7 | 8 | let IntType = 0 9 | let StringType = 1 10 | let BytesType = 2 11 | #-------------------Base functions---------------------- 12 | func getNumberByKey(key: String) = { 13 | match (getInteger(this, key)) { 14 | case v:Int => v 15 | case _ => 0 16 | } 17 | } 18 | 19 | func getBytesByKey(key: String) = { 20 | match (getBinary(this, key)) { 21 | case v:ByteVector => v 22 | case _ => base64'0' 23 | } 24 | } 25 | 26 | func getStringByKey(key: String) = { 27 | match (getString(this, key)) { 28 | case v:String => v 29 | case _ => "" 30 | } 31 | } 32 | 33 | func getStringByAddressAndKey(address: Address,key: String) = { 34 | match (getString(address, key)) { 35 | case v:String => v 36 | case _ => "" 37 | } 38 | } 39 | func getNumberByAddressAndKey(address: Address, key: String) = { 40 | match (getInteger(address, key)) { 41 | case v:Int => v 42 | case _ => 0 43 | } 44 | } 45 | #-----------------------Key----------------------------- 46 | let OraclesKey = "oracles" 47 | let SubscriberAddressKey = "subscriber_address" 48 | let TypeKey = "type" 49 | let GravityContractKey = "gravity_contract" 50 | let BftCoefficientKey = "bft_coefficient" 51 | 52 | let LastHeightKey = "last_height" 53 | let LastRoundKey = "last_round" 54 | let LastPulseIdKey = "last_pulse_id" 55 | 56 | func getHashDataKey(pulseId: Int) = "data_hash_" + toString(pulseId) 57 | func getHeightByPulseKey(pulseId: Int) = "height_" + toString(pulseId) 58 | func ConsulsKey(round: Int) = "consuls_" + toString(round) 59 | 60 | #-------------------Global vars------------------------- 61 | let oracles = getStringByKey(OraclesKey).split(",") 62 | let bftCoefficient = getNumberByKey(BftCoefficientKey) 63 | let gracityContract = addressFromStringValue(getStringByKey(GravityContractKey)) 64 | let lastGravityRound = getNumberByAddressAndKey(gracityContract, LastRoundKey) 65 | let consuls = getStringByAddressAndKey(gracityContract, ConsulsKey(lastGravityRound)).split(",") 66 | let subscriberAddress = getStringByKey(SubscriberAddressKey) 67 | let type = getNumberByKey(TypeKey) 68 | let lastPulseId = getNumberByKey(LastPulseIdKey) 69 | 70 | func getHashData(pulseId: Int) = getBytesByKey(getHashDataKey(pulseId)) 71 | func getHeightByPulse(pulseId: Int) = getNumberByKey(getHeightByPulseKey(pulseId)) 72 | 73 | func validateSign(hash: ByteVector, sign: String, oracle: String) = { 74 | if (sign != "nil") then 75 | (if sigVerify(hash, fromBase58String(sign), fromBase58String(oracle)) then 1 else 0) 76 | else 0 77 | } 78 | #-------------------Callable---------------------------- 79 | @Callable(i) 80 | func sendHashValue(hash: ByteVector, signs: String) = { 81 | let signList = signs.split(",") 82 | let count = 83 | validateSign(hash, signList[0], oracles[0]) 84 | + validateSign(hash, signList[1], oracles[1]) 85 | + validateSign(hash, signList[2], oracles[2]) 86 | + validateSign(hash, signList[3], oracles[3]) 87 | + validateSign(hash, signList[4], oracles[4]) 88 | 89 | if (count < bftCoefficient) 90 | then throw("invalid bft count") 91 | else { 92 | let currentPulseId = lastPulseId + 1 93 | WriteSet([ 94 | DataEntry(getHashDataKey(currentPulseId), hash), 95 | DataEntry(getHeightByPulseKey(currentPulseId), height), 96 | DataEntry(LastHeightKey, height), 97 | DataEntry(LastPulseIdKey, currentPulseId) 98 | ]) 99 | } 100 | } 101 | 102 | @Callable(i) 103 | func updateOracles(newSortedOracles: String, stringSigns: String, round: Int) = { 104 | let signs = stringSigns.split(",") 105 | let count = validateSign(toBytes(newSortedOracles), signs[0], consuls[0]) + 106 | validateSign(toBytes(newSortedOracles), signs[1], consuls[1]) + 107 | validateSign(toBytes(newSortedOracles), signs[2], consuls[2]) + 108 | validateSign(toBytes(newSortedOracles), signs[3], consuls[3]) + 109 | validateSign(toBytes(newSortedOracles), signs[4], consuls[4]) 110 | 111 | if(count < bftCoefficient) 112 | then throw("invalid bft count") 113 | else { 114 | WriteSet([ 115 | DataEntry(OraclesKey, newSortedOracles), 116 | DataEntry(LastRoundKey + "_" + toString(round), round) 117 | ]) 118 | } 119 | } 120 | 121 | @Verifier(i) 122 | func sendValueToSub() = { 123 | match (i) { 124 | case invokeTx:InvokeScriptTransaction => 125 | let vBytes = { 126 | if (type == IntType) then { 127 | let v = match (invokeTx.args[0]) { 128 | case v:Int => v 129 | case _ => throw("invalid value type") 130 | } 131 | toBytes(v) 132 | } else if (type == StringType) then { 133 | let v = match (invokeTx.args[0]) { 134 | case v:String => v 135 | case _ => throw("invalid value type") 136 | } 137 | toBytes(v) 138 | } else if (type == BytesType) then { 139 | let v = match (invokeTx.args[0]) { 140 | case v:ByteVector => v 141 | case _ => throw("invalid value type") 142 | } 143 | v 144 | } else 145 | throw("invalid value type") 146 | } 147 | let vPulseId = match (invokeTx.args[1]) { 148 | case vPulseId:Int => vPulseId 149 | case _ => throw("invalid height type") 150 | } 151 | 152 | if (invokeTx.function != "attachData") 153 | then throw("invalid function name") 154 | else if (invokeTx.args.size() != 2) 155 | then throw("invalid args size") 156 | else if (invokeTx.dApp != addressFromStringValue(subscriberAddress)) 157 | then throw("invalid dapp address") 158 | else if (getHeightByPulse(vPulseId) != height) 159 | then throw("invalid height") 160 | else if (getHashData(vPulseId) == base64'0') 161 | then throw("invalid pulse id") 162 | else if(keccak256(vBytes) != getHashData(vPulseId)) 163 | then throw("invalid keccak256(value)") 164 | else { 165 | true 166 | } 167 | case _ => sigVerify(i.bodyBytes, i.proofs[0], i.senderPublicKey) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /contracts/waves/subMockBytes.ride: -------------------------------------------------------------------------------- 1 | {-# STDLIB_VERSION 3 #-} 2 | {-# CONTENT_TYPE DAPP #-} 3 | {-# SCRIPT_TYPE ACCOUNT #-} 4 | 5 | @Callable(i) 6 | func attachValue(v: Int, height: Int) = { 7 | WriteSet([ 8 | DataEntry(toString(height), v) 9 | ]) 10 | } 11 | -------------------------------------------------------------------------------- /contracts/waves/subMockInt.ride: -------------------------------------------------------------------------------- 1 | {-# STDLIB_VERSION 3 #-} 2 | {-# CONTENT_TYPE DAPP #-} 3 | {-# SCRIPT_TYPE ACCOUNT #-} 4 | 5 | @Callable(i) 6 | func attachValue(v: ByteVector, height: Int) = { 7 | WriteSet([ 8 | DataEntry(toString(height), v), 9 | ]) 10 | } 11 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Gravity Node deployment 3 | 4 | This document represents manual for deployment the vitals parts of Gravity protocol entities. Such as: 5 | 6 | 1. Gravity Ledger Node - performs as basic chain operations, validates blocks, has public and private API. 7 | 2. Gravity Oracle service - plays a role of data provider in the system. Requires to provide these parameters: Nebula-SC address in target chain, target chain enum (`waves` or `ethereum`), target chain public node RPC, extractor URL. 8 | 9 | ### Introduction 10 | 11 | Gravity Ledger Node as well as the Oracle service can be pulled from our public [Docker registry](https://hub.docker.com/u/gravityhuborg). 12 | 13 | `docker pull gravityhuborg/gravity-ledger` 14 | `docker pull gravityhuborg/gravity-oracle` 15 | 16 | ### Ledger 17 | 18 | Ledger node supports different networks. Right now the node is bound to waves & ethereum chains. In fact, it's configuration for Docker image looks like this. 19 | 20 | 21 | |ENV var name|Type|Default Value | Description| 22 | |-------|-------|-----|--| 23 | | `GRAVITY_HOME` | `URI` | - | `Unified resource identifier for root directory of the Ledger node. Used to store configuration files.` 24 | | `GRAVITY_BOOTSTRAP` | `URL` | - | `Gravity Bootstrap Node URL` 25 | | `GRAVITY_RPC` | `URL` |`"127.0.0.1:2500"`| `RPC of your Node` 26 | | `GRAVITY_NETWORK` | `'devnet' | 'customnet'` |`devnet`| `Network enum` 27 | | `INIT_CONFIG` | `0 | 1` |`1`| `Does your node requires initial configuration? If '1' is provided 'gravity init' is run.` 28 | | `ETH_NODE_URL` | `URL` |`https://ropsten.infura.io/v3/55ce99b713ee4918896e979d172109cf`| `Ethereum node URL` 29 | | `GRAVITY_ETH_ADDRESS` | `string` | - |`Ethereum Address with 0x` 30 | | `GRAVITY_WAVES_ADDRESS` | `string` | - | `Waves Address` 31 | | `GRAVITY_WAVES_CHAINID` | `'S' | 'W' | 'T'` | - | `Chain ID of Waves chain` 32 | 33 | ### Run scripts for Ledger 34 | 35 | For ease of usage. If you want to run the Node in devnet, simply run: 36 | 37 | ```bash 38 | bash docker/deploy.sh --dev 39 | ``` 40 | 41 | For custom net: 42 | ```bash 43 | bash docker/deploy.sh --custom 44 | ``` 45 | 46 | If you want to run ledger and empty your ```$GRAVITY_HOME``` directory, run: 47 | 48 | ``` 49 | bash docker/deploy.sh --pure --dev 50 | ``` 51 | -------------------------------------------------------------------------------- /docker/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | build_oracle() { 4 | cd .. 5 | docker build -t gravity-oracle -f docker/oracle.dockerfile . 6 | } 7 | 8 | build_ledger() { 9 | cd .. 10 | docker build -t gravity-ledger -f docker/ledger.dockerfile . 11 | } 12 | 13 | main() { 14 | 15 | while [ -n "$1" ] 16 | do 17 | case "$1" in 18 | --ledger) build_ledger ;; 19 | --oracle) build_oracle ;; 20 | *) echo "Enter --oracle or --ledger in order to run operation"; exit 1; ;; 21 | esac 22 | 23 | exit 0; 24 | done 25 | } 26 | 27 | main $@ 28 | -------------------------------------------------------------------------------- /docker/deploy-oracle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | NEBULA_ADDRESS='0X124234234' 5 | CHAIN_TYPE='waves' 6 | 7 | # GRAVITY_HOME=/etc/gravity-oracle 8 | GRAVITY_PUBLIC_LEDGER_RPC='A' 9 | GRAVITY_TARGET_CHAIN_NODE_URL='B' 10 | GRAVITY_EXTRACTOR_ENDPOINT='C' 11 | 12 | 13 | docker run -it \ 14 | -e NEBULA_ADDRESS=$NEBULA_ADDRESS \ 15 | -e CHAIN_TYPE=$CHAIN_TYPE \ 16 | -e GRAVITY_PUBLIC_LEDGER_RPC=$GRAVITY_PUBLIC_LEDGER_RPC \ 17 | -e GRAVITY_TARGET_CHAIN_NODE_URL=$GRAVITY_TARGET_CHAIN_NODE_URL \ 18 | -e GRAVITY_EXTRACTOR_ENDPOINT=$GRAVITY_EXTRACTOR_ENDPOINT \ 19 | gravity-oracle 20 | -------------------------------------------------------------------------------- /docker/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | start_mainnet() { 4 | docker run -itd \ 5 | -e INIT_CONFIG=1 \ 6 | -e GRAVITY_NETWORK=mainnet \ 7 | -p 26657:26657 -p 2500:2500 -v "$GRAVITY_HOME":/etc/gravity gravityhuborg/gravity-ledger:master 8 | } 9 | 10 | start_devnet() { 11 | docker run -itd -p 26657:26657 -p 2500:2500 -v "$GRAVITY_HOME":/etc/gravity gravityhuborg/gravity-ledger:master 12 | } 13 | 14 | while [ -n "$1" ] 15 | do 16 | case "$1" in 17 | --pure) rm -rf "$GRAVITY_HOME" ;; 18 | # --custom) start_customnet ;; 19 | --mainnet) start_mainnet ;; 20 | # --dev) start_devnet ;; 21 | esac 22 | shift 23 | done 24 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '1.0' 2 | 3 | 4 | services: 5 | ledger: 6 | build: gravityhuborg/gravity-ledger 7 | volumes: 8 | - .$GRAVITY_HOME:/etc/gravity 9 | -------------------------------------------------------------------------------- /docker/entrypoint-ledger.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | override_config() { 6 | local relevant_path=$1 7 | local override_path=$2 8 | 9 | temp_config="/etc/gravity/config_tmp.json" 10 | 11 | cat "$relevant_path" | jq ". += $(echo $(cat $override_path))" > "$temp_config" 12 | 13 | cat $temp_config > "$relevant_path" 14 | rm $temp_config 15 | 16 | } 17 | 18 | if [ "$INIT_CONFIG" -eq 1 ] 19 | then 20 | # Config folder is empty, generating keys 21 | if [ -z "$(ls -A /etc/gravity/)" ]; then 22 | /bin/gravity ledger --home=/etc/gravity/ init --network="$GRAVITY_NETWORK" 23 | fi 24 | fi 25 | 26 | if [ ! -z $ADAPTERS_CFG_PATH ] 27 | then 28 | override_config '/etc/gravity/config.json' "$ADAPTERS_CFG_PATH" 29 | fi 30 | 31 | if [ ! -z $GENESIS_CFG_PATH ] 32 | then 33 | override_config '/etc/gravity/genesis.json' "$GENESIS_CFG_PATH" 34 | fi 35 | 36 | if [ -n "$GRAVITY_BOOTSTRAP" ] 37 | then 38 | /bin/gravity ledger --home=/etc/gravity start --rpc="$GRAVITY_PRIVATE_RPC" --bootstrap="$GRAVITY_BOOTSTRAP" 39 | else 40 | /bin/gravity ledger --home=/etc/gravity start --rpc="$GRAVITY_PRIVATE_RPC" --bootstrap="" 41 | fi 42 | -------------------------------------------------------------------------------- /docker/entrypoint-oracle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | init_oracle() { 5 | /bin/gravity oracle --home=/etc/gravity init \ 6 | $NEBULA_ADDRESS \ 7 | $CHAIN_TYPE \ 8 | $GRAVITY_PUBLIC_LEDGER_RPC \ 9 | $GRAVITY_TARGET_CHAIN_NODE_URL \ 10 | $GRAVITY_EXTRACTOR_ENDPOINT 11 | } 12 | 13 | start_oracle() { 14 | /bin/gravity oracle --home=/etc/gravity \ 15 | start \ 16 | $NEBULA_ADDRESS 17 | } 18 | 19 | if [ "$INIT_CONFIG" -eq 1 ] 20 | then 21 | if [ -z "$(ls -A /etc/gravity/)" ]; then 22 | init_oracle 23 | fi 24 | fi 25 | 26 | start_oracle 27 | -------------------------------------------------------------------------------- /docker/ledger.dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.16-buster as ledger 2 | 3 | WORKDIR /node 4 | 5 | COPY . /node 6 | 7 | RUN chmod 777 docker/entrypoint-ledger.sh 8 | 9 | RUN cd cmd/gravity/ && \ 10 | go build -o gravity && \ 11 | chmod 777 gravity 12 | 13 | FROM golang:1.16-buster 14 | 15 | COPY --from=ledger /node/docker/entrypoint-ledger.sh . 16 | COPY --from=ledger /node/cmd/gravity/gravity /bin 17 | 18 | ARG GRAVITY_BOOTSTRAP="" 19 | ARG GRAVITY_PRIVATE_RPC="127.0.0.1:2500" 20 | ARG GRAVITY_NETWORK=devnet 21 | ARG INIT_CONFIG=1 22 | ARG ADAPTERS_CFG_PATH='' 23 | ARG GENESIS_CFG_PATH='' 24 | 25 | ENV GRAVITY_BOOTSTRAP=$GRAVITY_BOOTSTRAP 26 | ENV GRAVITY_RPC=$GRAVITY_RPC 27 | ENV GRAVITY_NETWORK=$GRAVITY_NETWORK 28 | ENV INIT_CONFIG=$INIT_CONFIG 29 | 30 | ENV ADAPTERS_CFG_PATH=$ADAPTERS_CFG_PATH 31 | ENV GENESIS_CFG_PATH=$GENESIS_CFG_PATH 32 | 33 | VOLUME /etc/gravity/ 34 | 35 | ENTRYPOINT ["/bin/sh", "./entrypoint-ledger.sh"] 36 | -------------------------------------------------------------------------------- /docker/oracle.dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.16-alpine as oracle 2 | 3 | WORKDIR /node 4 | 5 | RUN apk update \ 6 | && apk --no-cache --update add build-base linux-headers 7 | 8 | COPY . /node 9 | 10 | RUN chmod 755 docker/entrypoint-oracle.sh 11 | 12 | RUN cd cmd/gravity/ && \ 13 | go build -o gravity && \ 14 | chmod 777 gravity 15 | 16 | COPY docker/entrypoint-oracle.sh /node 17 | 18 | FROM golang:alpine 19 | 20 | ENV NEBULA_ADDRESS='' 21 | ENV CHAIN_TYPE='' 22 | 23 | ENV GRAVITY_PUBLIC_LEDGER_RPC='' 24 | ENV GRAVITY_TARGET_CHAIN_NODE_URL='' 25 | ENV GRAVITY_EXTRACTOR_ENDPOINT='' 26 | 27 | ENV INIT_CONFIG=0 28 | 29 | COPY --from=oracle /node/entrypoint-oracle.sh . 30 | COPY --from=oracle /node/cmd/gravity/gravity . 31 | COPY --from=oracle /node/cmd/gravity/gravity /bin/gravity 32 | 33 | VOLUME /etc/gravity 34 | 35 | ENTRYPOINT ["/bin/sh", "./entrypoint-oracle.sh"] 36 | -------------------------------------------------------------------------------- /docker/run-oracle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/README-ru.md: -------------------------------------------------------------------------------- 1 | # Gravity Core 2 | 3 | ## Init ledger 4 | 5 | gravity ledger --home={home} init --network=devnet 6 | 7 | home - домашная директория для gravity 8 | network - указания сети для которой нужно сгенерировать конфиги 9 | 10 | Для иницилизации конфигурация ledger для devnet надо вызвать команду: 11 | 12 | gravity ledger --home={home} init 13 | 14 | Для иницилизации конфигурация ledger для собственной сети надо вызвать команду: 15 | 16 | gravity ledger --home={home} init --network=custom 17 | 18 | Если в директории нет privKey.json, то он автоматически сгенерируется. 19 | 20 | Файлы конфигурации: 21 | genesis.json - генезис блок для леджера 22 | 23 | Некоторая часть конфигураций описана [здесь](https://docs.tendermint.com/master/tendermint-core/using-tendermint.html), но есть пару кастомных конфигураций: 24 | 25 | "сonsulsCount": 5, - количество консулов 26 | "initScore": { 27 | "{pubKey валидатора}": {значение gravity score}, 28 | }, 29 | "oraclesAddressByValidator": {. - паблик ключи валидаторов находящихся в InitScore 30 | "{pubKey валидатора}": { 31 | "ethereum": "{pubKey ethereum}", 32 | "waves": "{pubKey waves}" 33 | } 34 | } 35 | 36 | config.json - конфигурации ledger ноды 37 | 38 | Большая часть конфигураций описана [здесь](https://docs.tendermint.com/master/tendermint-core/configuration.html), но есть пару кастомных конфигураций: 39 | 40 | "adapters": { 41 | "ethereum": { 42 | "nodeUrl": "", - адрес ethereum ноды 43 | "gravityContractAddress": "" - адрес gravity контракта в ethereum 44 | }, 45 | "waves": { 46 | "NodeUrl": "", - адрес waves ноды 47 | "ChainId": "S", - chainId адрес waves ноды 48 | "GravityContractAddress": - адрес gravity контракта в waves 49 | } 50 | } 51 | 52 | key_state.json - состояние ключа валидатора (tendermint) 53 | 54 | node_key.json - приватный ключ ноды ledger (не приватный ключ валидатора) (tendermint) 55 | 56 | Чтобы валидатор мог учавствовать в консенсусе, нужно чтобы остальные выставили ему оценку (gravity score). Если оценка валидатора больше 0, 57 | то он является полноправным участником консеснуса и может претендовать на консульство. 58 | 59 | ## RPC 60 | В gravity ledger есть 2 вида rpc: 61 | * публичный, устанавливается в config.json RPC 62 | * приватный, устанавливается во флагах при запуске. Стандартное значение: 127.0.0.1:2500 63 | 64 | ## Start ledger 65 | 66 | gravity ledger --home={home} start --rpc="127.0.0.1:2500" --bootstrap="http://127.0.0.1:26657" 67 | 68 | home - домашная директория с конфигурациями для gravity 69 | rpc - host для приватного rpc 70 | bootstrap - публичный rpc bootstrap ноды к которой подключиться валидатор 71 | 72 | Для запуска ноды в devnet нужно ввести команд: 73 | 74 | gravity ledger —home={home} start 75 | 76 | Для запуска первой ноды в своей сети: 77 | 78 | gravity ledger —home={home} start --bootstrap="" 79 | 80 | Для запуска остальных нод в своей сети: 81 | 82 | gravity ledger —home={home} start --bootstrap={url к публичному rpc первой ноды из config.json} 83 | 84 | —rpc="127.0.0.1:2500" - приватный rpc 85 | —bootstrap="" - url bootstrap ноды к которой нужно подключаться 86 | 87 | Если вы поднимаете сеть локально, то вы не сможете поднять больше 2-х год на 1 адресе (ограничения tendermint). 88 | 89 | Остальная информацию по соединению нод лежит [здесь](https://docs.tendermint.com/master/spec/p2p/#) 90 | 91 | ## Voting 92 | Чтобы добавить нового валидатора в консенсус, нужно проголосовать за его gravity score. 93 | Gravity ledger должен содержать минимум 3 валидатора. 94 | 95 | Для выставления своей оценки нужно отправит запрос к приватному rpc: 96 | 97 | Route: http://{priv rpc host}/vote 98 | 99 | { 100 | "votes": [ 101 | { 102 | PubKey: {публичный ключ валидатора}, 103 | Score: {ваша оценка к нему от 0 до 100} 104 | }... 105 | ] 106 | } 107 | 108 | Если вы отправите запрос без валидаторов, которых вы указывали ранее, то ваше оценка к ним будет изменена на 0. 109 | 110 | ## Create Nebula 111 | Для того, чтобы добавить небулу нужно отправит запрос к приватному rpc: 112 | 113 | Route: http://{priv rpc host}//setNebula 114 | 115 | { 116 | "NebulaId": "адрес небулы" 117 | "ChainType": "ethereum/waves" 118 | "MaxPulseCountInBlock": {колчиество максимальный пулсов в блок} 119 | "MinScore": {минимальный скор для участия в небуле} 120 | } 121 | 122 | ## Init oracle 123 | 124 | gravity oracle --home={home} init <адрес небулы> " 125 | 126 | После выполения команды в {home} директории появится папка nebulae с файлом {address небулы}.json . 127 | Для более детальной настройки его можно отредактировать самостоятельно. 128 | 129 | ## Start oracle 130 | 131 | gravity oracle --home={home} start <адрес небулы> 132 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Gravity-Tech/gravity-core 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/ThreeDotsLabs/watermill v1.1.1 7 | github.com/btcsuite/btcd v0.21.0-beta // indirect 8 | github.com/btcsuite/btcutil v1.0.2 9 | github.com/dgraph-io/badger v1.6.1 10 | github.com/ethereum/go-ethereum v1.9.25 11 | github.com/gookit/validate v1.2.8 12 | github.com/labstack/echo/v4 v4.3.0 13 | github.com/lithammer/shortuuid/v3 v3.0.7 // indirect 14 | github.com/mitchellh/mapstructure v1.4.1 15 | github.com/mr-tron/base58 v1.2.0 16 | github.com/near/borsh-go v0.3.0 17 | github.com/novifinancial/serde-reflection/serde-generate/runtime/golang v0.0.0-20210311194640-4c3416aad7d0 18 | github.com/portto/solana-go-sdk v0.0.0-20210521084441-878620557359 19 | github.com/stretchr/testify v1.7.0 // indirect 20 | github.com/tendermint/tendermint v0.33.4 21 | github.com/tyler-smith/go-bip39 v1.1.0 // indirect 22 | github.com/urfave/cli/v2 v2.3.0 23 | github.com/wavesplatform/go-lib-crypto v0.0.0-20190905125804-474f21517ad5 24 | github.com/wavesplatform/gowaves v0.8.7 25 | go.uber.org/multierr v1.6.0 // indirect 26 | go.uber.org/zap v1.16.0 27 | ) 28 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "@typechain/hardhat"; 2 | import "@nomiclabs/hardhat-ethers"; 3 | import "@nomiclabs/hardhat-waffle"; 4 | import "solidity-coverage"; 5 | 6 | /** 7 | * @type import('hardhat/config').HardhatUserConfig 8 | */ 9 | export default { 10 | solidity: { 11 | version: "0.7.0", 12 | settings: { 13 | optimizer: { 14 | enabled: true, 15 | runs: 200 16 | } 17 | } 18 | }, 19 | abiExporter: { 20 | clear: true, 21 | flat: true, 22 | spacing: 2 23 | }, 24 | mocha: { 25 | timeout: '10000000000' 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /ledger/query/consuls.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/Gravity-Tech/gravity-core/common/account" 7 | "github.com/Gravity-Tech/gravity-core/common/storage" 8 | ) 9 | 10 | type SignByConsulRq struct { 11 | ConsulPubKey string 12 | ChainType account.ChainType 13 | NebulaId string 14 | RoundId int64 15 | } 16 | 17 | func consuls(store *storage.Storage) ([]storage.Consul, error) { 18 | v, err := store.Consuls() 19 | if err != nil && err != storage.ErrKeyNotFound { 20 | return nil, err 21 | } 22 | 23 | return v, nil 24 | } 25 | 26 | func consulsCandidate(store *storage.Storage) ([]storage.Consul, error) { 27 | v, err := store.ConsulsCandidate() 28 | if err != nil && err != storage.ErrKeyNotFound { 29 | return nil, err 30 | } 31 | 32 | return v, nil 33 | } 34 | 35 | func signNewConsulsByConsul(store *storage.Storage, value []byte) ([]byte, error) { 36 | var rq SignByConsulRq 37 | err := json.Unmarshal(value, &rq) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | consul, err := account.HexToValidatorPubKey(rq.ConsulPubKey) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | v, err := store.SignConsulsByConsul(consul, rq.ChainType, rq.RoundId) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return v, nil 53 | } 54 | 55 | func signNewOraclesByConsul(store *storage.Storage, value []byte) ([]byte, error) { 56 | var rq SignByConsulRq 57 | err := json.Unmarshal(value, &rq) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | consul, err := account.HexToValidatorPubKey(rq.ConsulPubKey) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | nebula, err := account.StringToNebulaId(rq.NebulaId, rq.ChainType) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | v, err := store.SignOraclesByConsul(consul, nebula, rq.RoundId) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | return v, nil 78 | } 79 | -------------------------------------------------------------------------------- /ledger/query/oracles.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/Gravity-Tech/gravity-core/common/account" 6 | "github.com/Gravity-Tech/gravity-core/common/storage" 7 | "github.com/ethereum/go-ethereum/common/hexutil" 8 | ) 9 | 10 | type ByValidatorRq struct { 11 | PubKey string 12 | } 13 | 14 | type ByNebulaRq struct { 15 | ChainType account.ChainType 16 | NebulaAddress string 17 | } 18 | 19 | type ResultsRq struct { 20 | ChainType account.ChainType 21 | Height uint64 22 | NebulaAddress string 23 | } 24 | 25 | func allValidators(store *storage.Storage, _ []byte) ([]byte, error) { 26 | scores, err := store.Scores() 27 | result := make([]string, len(scores)) 28 | 29 | if err != nil { 30 | return make([]byte, 0), err 31 | } 32 | 33 | for consulPubKey, _ := range scores { 34 | result = append(result, hexutil.Encode(consulPubKey[:])) 35 | } 36 | 37 | encoded, err := json.Marshal(result) 38 | 39 | if err != nil { 40 | return make([]byte, 0), err 41 | } 42 | 43 | return encoded, nil 44 | } 45 | 46 | func oraclesByValidator(store *storage.Storage, value []byte) (storage.OraclesByTypeMap, error) { 47 | var rq ByValidatorRq 48 | err := json.Unmarshal(value, &rq) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | pubKey, err := account.HexToValidatorPubKey(rq.PubKey) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | v, err := store.OraclesByConsul(pubKey) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | return v, nil 64 | } 65 | 66 | func oraclesByNebula(store *storage.Storage, value []byte) (storage.OraclesMap, error) { 67 | var rq ByNebulaRq 68 | err := json.Unmarshal(value, &rq) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | nebula, err := account.StringToNebulaId(rq.NebulaAddress, rq.ChainType) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | v, err := store.OraclesByNebula(nebula) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | return v, nil 84 | } 85 | 86 | func bftOraclesByNebula(store *storage.Storage, value []byte) (storage.OraclesMap, error) { 87 | var rq ByNebulaRq 88 | err := json.Unmarshal(value, &rq) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | nebula, err := account.StringToNebulaId(rq.NebulaAddress, rq.ChainType) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | v, err := store.BftOraclesByNebula(nebula) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | return v, nil 104 | } 105 | 106 | func results(store *storage.Storage, value []byte) ([]string, error) { 107 | var rq ResultsRq 108 | err := json.Unmarshal(value, &rq) 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | nebula, err := account.StringToNebulaId(rq.NebulaAddress, rq.ChainType) 114 | if err != nil { 115 | return nil, err 116 | } 117 | 118 | v, err := store.Results(nebula, rq.Height) 119 | if err != nil { 120 | return nil, err 121 | } 122 | 123 | return v, nil 124 | } 125 | 126 | func nebulaOraclesIndex(store *storage.Storage, value []byte) (uint64, error) { 127 | var rq ByNebulaRq 128 | err := json.Unmarshal(value, &rq) 129 | if err != nil { 130 | return 0, err 131 | } 132 | 133 | nebula, err := account.StringToNebulaId(rq.NebulaAddress, rq.ChainType) 134 | if err != nil { 135 | return 0, err 136 | } 137 | 138 | v, err := store.NebulaOraclesIndex(nebula) 139 | if err != nil { 140 | return 0, err 141 | } 142 | 143 | return v, nil 144 | } 145 | -------------------------------------------------------------------------------- /ledger/query/query.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/Gravity-Tech/gravity-core/common/storage" 9 | "github.com/Gravity-Tech/gravity-core/config" 10 | ) 11 | 12 | type Path string 13 | 14 | const ( 15 | OracleByValidatorPath Path = "oraclesByValidator" 16 | OracleByNebulaPath Path = "oraclesByNebula" 17 | BftOracleByNebulaPath Path = "bftOraclesByNebula" 18 | RoundHeightPath Path = "roundHeight" 19 | CommitHashPath Path = "commitHash" 20 | RevealPath Path = "reveal" 21 | RevealsPath Path = "reveals" 22 | ResultPath Path = "result" 23 | ResultsPath Path = "results" 24 | NebulaePath Path = "nebulae" 25 | NebulaInfoPath Path = "nebula_info" 26 | LastRoundApprovedPath Path = "lastRoundApproved" 27 | ConsulsPath Path = "consuls" 28 | ConsulsCandidatePath Path = "consulsCandidate" 29 | SignNewConsulsByConsulPath Path = "signNewConsulsByConsul" 30 | SignNewOraclesByConsulPath Path = "signNewOraclesByConsul" 31 | NebulaOraclesIndexPath Path = "nebulaOraclesIndex" 32 | AllValidatorsPath Path = "allValidators" 33 | ValidatorDetailsPath Path = "validatorDetails" 34 | NebulaCustomParams Path = "nebulaCustomParams" 35 | ) 36 | 37 | var ( 38 | ErrInvalidPath = errors.New("invalid path") 39 | ErrValueNotFound = errors.New("value not found") 40 | ) 41 | 42 | func Query(store *storage.Storage, path string, rq []byte, validatorDetails *config.ValidatorDetails) ([]byte, error) { 43 | var value interface{} 44 | var err error 45 | switch Path(path) { 46 | case OracleByValidatorPath: 47 | value, err = oraclesByValidator(store, rq) 48 | case OracleByNebulaPath: 49 | value, err = oraclesByNebula(store, rq) 50 | case RoundHeightPath: 51 | value, err = roundHeight(store, rq) 52 | case CommitHashPath: 53 | value, err = commitHash(store, rq) 54 | case RevealPath: 55 | value, err = reveal(store, rq) 56 | case RevealsPath: 57 | value, err = reveals(store, rq) 58 | case ResultPath: 59 | value, err = result(store, rq) 60 | case BftOracleByNebulaPath: 61 | value, err = bftOraclesByNebula(store, rq) 62 | case ResultsPath: 63 | value, err = results(store, rq) 64 | case NebulaePath: 65 | value, err = nebulae(store) 66 | case NebulaInfoPath: 67 | value, err = nebulaInfo(store, rq) 68 | case ConsulsPath: 69 | value, err = consuls(store) 70 | case ConsulsCandidatePath: 71 | value, err = consulsCandidate(store) 72 | case SignNewConsulsByConsulPath: 73 | value, err = signNewConsulsByConsul(store, rq) 74 | case SignNewOraclesByConsulPath: 75 | value, err = signNewOraclesByConsul(store, rq) 76 | case NebulaOraclesIndexPath: 77 | value, err = nebulaOraclesIndex(store, rq) 78 | case LastRoundApprovedPath: 79 | value, err = store.LastRoundApproved() 80 | case AllValidatorsPath: 81 | value, err = allValidators(store, rq) 82 | case ValidatorDetailsPath: 83 | value, err = validatorDetails.Bytes() 84 | case NebulaCustomParams: 85 | value, err = nebulaCustomParams(store, rq) 86 | default: 87 | return nil, ErrInvalidPath 88 | } 89 | 90 | if err != nil && err != storage.ErrKeyNotFound { 91 | return nil, err 92 | } else if err == storage.ErrKeyNotFound { 93 | return nil, ErrValueNotFound 94 | } 95 | 96 | b, err := toBytes(value) 97 | if err != nil { 98 | return nil, err 99 | } 100 | 101 | return b, nil 102 | } 103 | 104 | func toBytes(v interface{}) ([]byte, error) { 105 | var err error 106 | var b []byte 107 | switch v.(type) { 108 | case uint64: 109 | b = make([]byte, 8, 8) 110 | binary.BigEndian.PutUint64(b, v.(uint64)) 111 | case []byte: 112 | b = v.([]byte) 113 | default: 114 | b, err = json.Marshal(v) 115 | } 116 | 117 | if err != nil { 118 | return nil, err 119 | } 120 | 121 | return b, nil 122 | } 123 | -------------------------------------------------------------------------------- /ledger/query/round.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/Gravity-Tech/gravity-core/common/account" 7 | 8 | "github.com/ethereum/go-ethereum/common/hexutil" 9 | 10 | "github.com/Gravity-Tech/gravity-core/common/storage" 11 | ) 12 | 13 | type RoundHeightRq struct { 14 | ChainType account.ChainType 15 | LedgerHeight uint64 16 | } 17 | type CommitHashRq struct { 18 | ChainType account.ChainType 19 | NebulaAddress string 20 | Height int64 21 | PulseId int64 22 | OraclePubKey string 23 | } 24 | type RevealRq struct { 25 | ChainType account.ChainType 26 | NebulaAddress string 27 | OraclePubKey string 28 | Height int64 29 | PulseId int64 30 | CommitHash string 31 | } 32 | type ResultRq struct { 33 | ChainType account.ChainType 34 | NebulaAddress string 35 | Height int64 36 | OraclePubKey string 37 | } 38 | 39 | func roundHeight(store *storage.Storage, value []byte) (uint64, error) { 40 | var rq RoundHeightRq 41 | err := json.Unmarshal(value, &rq) 42 | if err != nil { 43 | return 0, err 44 | } 45 | 46 | return store.RoundHeight(rq.ChainType, rq.LedgerHeight) 47 | } 48 | func commitHash(store *storage.Storage, value []byte) ([]byte, error) { 49 | var rq CommitHashRq 50 | err := json.Unmarshal(value, &rq) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | nebulaAddress, err := account.StringToNebulaId(rq.NebulaAddress, rq.ChainType) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | oraclePubKey, err := account.StringToOraclePubKey(rq.OraclePubKey, rq.ChainType) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | v, err := store.CommitHash(nebulaAddress, rq.Height, rq.PulseId, oraclePubKey) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | return v, nil 71 | } 72 | func reveal(store *storage.Storage, value []byte) ([]byte, error) { 73 | var rq RevealRq 74 | err := json.Unmarshal(value, &rq) 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | nebulaAddress, err := account.StringToNebulaId(rq.NebulaAddress, rq.ChainType) 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | commitHash, err := hexutil.Decode(rq.CommitHash) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | oraclePubKey, err := account.StringToOraclePubKey(rq.OraclePubKey, rq.ChainType) 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | v, err := store.Reveal(nebulaAddress, rq.Height, rq.PulseId, commitHash, oraclePubKey) 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | return v, nil 100 | } 101 | func reveals(store *storage.Storage, value []byte) ([]string, error) { 102 | var rq RevealRq 103 | err := json.Unmarshal(value, &rq) 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | nebulaAddress, err := account.StringToNebulaId(rq.NebulaAddress, rq.ChainType) 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | v, err := store.Reveals(nebulaAddress, rq.Height, rq.PulseId) 114 | if err != nil { 115 | return nil, err 116 | } 117 | 118 | return v, nil 119 | } 120 | func result(store *storage.Storage, value []byte) ([]byte, error) { 121 | var rq ResultRq 122 | err := json.Unmarshal(value, &rq) 123 | if err != nil { 124 | return nil, err 125 | } 126 | 127 | nebulaAddress, err := account.StringToNebulaId(rq.NebulaAddress, rq.ChainType) 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | oraclePubKey, err := account.StringToOraclePubKey(rq.OraclePubKey, rq.ChainType) 133 | if err != nil { 134 | return nil, err 135 | } 136 | 137 | v, err := store.Result(nebulaAddress, rq.Height, oraclePubKey) 138 | if err != nil { 139 | return nil, err 140 | } 141 | 142 | return v, nil 143 | } 144 | func nebulae(store *storage.Storage) (storage.NebulaMap, error) { 145 | v, err := store.Nebulae() 146 | if err != nil { 147 | return nil, err 148 | } 149 | if len(v) == 0 { 150 | return nil, storage.ErrKeyNotFound 151 | } 152 | 153 | return v, nil 154 | } 155 | 156 | func nebulaInfo(store *storage.Storage, value []byte) (*storage.NebulaInfo, error) { 157 | var rq ByNebulaRq 158 | err := json.Unmarshal(value, &rq) 159 | if err != nil { 160 | return nil, err 161 | } 162 | 163 | nebulaAddress, err := account.StringToNebulaId(rq.NebulaAddress, rq.ChainType) 164 | if err != nil { 165 | return nil, err 166 | } 167 | 168 | v, err := store.NebulaInfo(nebulaAddress) 169 | if err != nil { 170 | return nil, err 171 | } 172 | 173 | return v, nil 174 | } 175 | func nebulaCustomParams(store *storage.Storage, value []byte) (*storage.NebulaCustomParams, error) { 176 | var rq ByNebulaRq 177 | err := json.Unmarshal(value, &rq) 178 | if err != nil { 179 | return nil, err 180 | } 181 | 182 | nebulaAddress, err := account.StringToNebulaId(rq.NebulaAddress, rq.ChainType) 183 | if err != nil { 184 | return nil, err 185 | } 186 | 187 | v, err := store.NebulaCustomParams(nebulaAddress) 188 | if err != nil { 189 | return nil, err 190 | } 191 | 192 | return v, nil 193 | } 194 | -------------------------------------------------------------------------------- /ledger/scheduler/event_processor.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | import ( 4 | "encoding/json" 5 | "sync" 6 | "time" 7 | 8 | "github.com/Gravity-Tech/gravity-core/common/account" 9 | "github.com/ThreeDotsLabs/watermill" 10 | "github.com/ThreeDotsLabs/watermill/message" 11 | "github.com/mitchellh/mapstructure" 12 | "go.uber.org/zap" 13 | ) 14 | 15 | type SchedulerEvent struct { 16 | Name string 17 | Params map[string]interface{} 18 | } 19 | type EventHandler func(event SchedulerEvent) error 20 | type EventServer struct { 21 | Handlers map[string]EventHandler 22 | } 23 | 24 | type UpdateOraclesRequest struct { 25 | NebulaKey string `mapstructure:"nebula_key"` 26 | ChainType account.ChainType `mapstructure:"chain_type"` 27 | Sender account.OraclesPubKey `mapstructure:"sender"` 28 | RoundId int64 `mapstructure:"round_id"` 29 | IsSender bool `mapstructure:"is_sender"` 30 | } 31 | 32 | func PublishMessage(topic string, rawmsg interface{}) { 33 | tosend, err := json.Marshal(rawmsg) 34 | if err != nil { 35 | zap.L().Error(err.Error()) 36 | return 37 | } 38 | msg := message.NewMessage(watermill.NewUUID(), tosend) 39 | if err := EventBus.Publish(topic, msg); err != nil { 40 | zap.L().Error(err.Error()) 41 | } 42 | } 43 | 44 | func NewEventServer() *EventServer { 45 | server := &EventServer{ 46 | Handlers: map[string]EventHandler{}, 47 | } 48 | server.SetHandler("handle_block", HandleBlock) 49 | server.SetHandler("update_oracles", UpdateOraclesHandler) 50 | 51 | return server 52 | } 53 | func (s *EventServer) SetHandler(name string, handler EventHandler) { 54 | s.Handlers[name] = handler 55 | } 56 | func (s *EventServer) handleEvent(event SchedulerEvent) { 57 | handler, ok := s.Handlers[event.Name] 58 | if ok { 59 | err := handler(event) 60 | if err != nil { 61 | zap.L().Error(err.Error()) 62 | return 63 | } 64 | } 65 | } 66 | func (s *EventServer) Serve(messages <-chan *message.Message) { 67 | go startHttpListener() 68 | for msg := range messages { 69 | var wg sync.WaitGroup 70 | lmsg := *msg 71 | wg.Add(1) 72 | go func(mwg *sync.WaitGroup, m message.Message) { 73 | zap.L().Sugar().Debug("Receive message", string(m.Payload)) 74 | defer wg.Done() 75 | event := SchedulerEvent{} 76 | err := json.Unmarshal(msg.Payload, &event) 77 | if err != nil { 78 | zap.L().Error(err.Error()) 79 | return 80 | } 81 | s.handleEvent(event) 82 | }(&wg, lmsg) 83 | 84 | msg.Ack() 85 | } 86 | } 87 | 88 | func HandleBlock(event SchedulerEvent) error { 89 | customEvent := struct { 90 | Height int64 `mapstructure:"height"` 91 | }{} 92 | err := mapstructure.Decode(event.Params, &customEvent) 93 | if err != nil { 94 | return err 95 | } 96 | zap.L().Sugar().Debug("Calling processor", customEvent) 97 | GlobalScheduler.Process(int64(customEvent.Height)) 98 | return nil 99 | } 100 | 101 | func UpdateOraclesHandler(event SchedulerEvent) error { 102 | 103 | Event := UpdateOraclesRequest{} 104 | err := mapstructure.Decode(event.Params, &Event) 105 | if err != nil { 106 | zap.L().Error(err.Error()) 107 | return err 108 | } 109 | 110 | nebulaId, err := account.StringToNebulaId(Event.NebulaKey, Event.ChainType) 111 | if err != nil { 112 | zap.L().Error(err.Error()) 113 | return err 114 | } 115 | GlobalScheduler.UpdateOracles(Event.RoundId, nebulaId, GlobalStorage) 116 | err = GlobalScheduler.signOraclesByNebula(Event.RoundId, nebulaId, Event.ChainType, Event.Sender) 117 | time.Sleep(time.Second * 5) 118 | 119 | success := false 120 | attempts := 4 121 | for { 122 | if success || attempts == 0 { 123 | break 124 | } 125 | 126 | if Event.IsSender { 127 | err = GlobalScheduler.sendOraclesToNebula(nebulaId, Event.ChainType, Event.RoundId) 128 | if err != nil { 129 | attempts -= 1 130 | continue 131 | } 132 | } 133 | success = true 134 | } 135 | 136 | //GlobalScheduler.Process(int64(customEvent.Height)) 137 | return nil 138 | } 139 | -------------------------------------------------------------------------------- /ledger/scheduler/http_listener.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/Gravity-Tech/gravity-core/common/account" 7 | "github.com/labstack/echo/v4" 8 | "go.uber.org/zap" 9 | ) 10 | 11 | func startHttpListener() { 12 | e := echo.New() 13 | e.POST("/webhooks", HttpUpdateOraclesHandler) 14 | e.Logger.Debug(e.Start("127.0.0.1:3501")) 15 | } 16 | 17 | func HttpUpdateOraclesHandler(c echo.Context) error { 18 | payload := struct { 19 | NebulaKey string `json:"nebula_key"` 20 | ChainType account.ChainType `json:"chain_type"` 21 | //RoundId int64 `json:"round_id"` 22 | }{} 23 | if err := c.Bind(&payload); err != nil { 24 | zap.L().Error(err.Error()) 25 | return err 26 | } 27 | zap.L().Sugar().Debug("payload ", payload) 28 | 29 | ManualUpdate.Active = true 30 | ManualUpdate.UpdateQueue = append(ManualUpdate.UpdateQueue, NebulaToUpdate{ 31 | Id: payload.NebulaKey, 32 | ChainType: payload.ChainType, 33 | }) 34 | zap.L().Sugar().Debugf("Added manual update nebula: ", ManualUpdate) 35 | return c.String(http.StatusOK, "OK") 36 | } 37 | -------------------------------------------------------------------------------- /oracle/extractor/client.go: -------------------------------------------------------------------------------- 1 | package extractor 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io/ioutil" 10 | "net/http" 11 | 12 | "go.uber.org/zap" 13 | ) 14 | 15 | const ( 16 | ExtractPath = "extract" 17 | InfoPath = "info" 18 | AggregatePath = "aggregate" 19 | 20 | String DataType = "string" 21 | Int64 DataType = "int64" 22 | Base64 DataType = "base64" 23 | ) 24 | 25 | var ( 26 | NotFoundErr = errors.New("data not found") 27 | ) 28 | 29 | type DataType string 30 | type Data struct { 31 | Type DataType 32 | Value string 33 | } 34 | 35 | type Client struct { 36 | hostUrl string 37 | } 38 | 39 | func New(hostUrl string) *Client { 40 | return &Client{ 41 | hostUrl: hostUrl, 42 | } 43 | } 44 | 45 | func (client *Client) Extract(ctx context.Context) (*Data, error) { 46 | rs, err := client.do(ExtractPath, http.MethodGet, nil, ctx) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | var result Data 52 | err = json.Unmarshal(rs, &result) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | return &result, nil 58 | } 59 | 60 | func (client *Client) Aggregate(values []Data, ctx context.Context) (*Data, error) { 61 | rs, err := client.do(AggregatePath, http.MethodPost, values, ctx) 62 | if err != nil { 63 | return nil, err 64 | } 65 | zap.L().Sugar().Debugf("Aggragate response: %s", string(rs)) 66 | var result Data 67 | err = json.Unmarshal(rs, &result) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | return &result, nil 73 | } 74 | 75 | func (client *Client) do(route string, method string, rqBody interface{}, ctx context.Context) ([]byte, error) { 76 | rqUrl := fmt.Sprintf("%v/%v", client.hostUrl, route) 77 | zap.L().Sugar().Debugf("Reuest URL: %s", rqUrl) 78 | var buf *bytes.Buffer 79 | var req *http.Request 80 | var err error 81 | if rqBody != nil { 82 | b, err := json.Marshal(&rqBody) 83 | if err != nil { 84 | return nil, err 85 | } 86 | zap.L().Sugar().Debugf("Reuest Body: %s", string(b)) 87 | buf = bytes.NewBuffer(b) 88 | req, err = http.NewRequestWithContext(ctx, method, rqUrl, buf) 89 | if err != nil { 90 | return nil, err 91 | } 92 | } else { 93 | req, err = http.NewRequestWithContext(ctx, method, rqUrl, nil) 94 | if err != nil { 95 | return nil, err 96 | } 97 | } 98 | req.Header.Set("Content-Type", "application/json") 99 | response, err := http.DefaultClient.Do(req) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | defer response.Body.Close() 105 | zap.L().Sugar().Debug("Response: ", response) 106 | if response.StatusCode == 404 { 107 | return nil, NotFoundErr 108 | } 109 | 110 | rsBody, err := ioutil.ReadAll(response.Body) 111 | if err != nil { 112 | return nil, err 113 | } 114 | 115 | return rsBody, nil 116 | } 117 | -------------------------------------------------------------------------------- /oracle/extractor/models.go: -------------------------------------------------------------------------------- 1 | package extractor 2 | 3 | type Info struct { 4 | Description string `json:"description"` 5 | DataFeedTag string `json:"datafeedtag"` 6 | } 7 | 8 | type DataRs struct { 9 | Value interface{} `json:"value"` 10 | } 11 | 12 | type AggregationRequestBody struct { 13 | Type string `json:"type"` 14 | Values []interface{} `json:"values"` 15 | } 16 | -------------------------------------------------------------------------------- /oracle/node/exe.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/Gravity-Tech/gravity-core/common/account" 8 | "github.com/Gravity-Tech/gravity-core/common/gravity" 9 | "github.com/Gravity-Tech/gravity-core/common/state" 10 | "github.com/Gravity-Tech/gravity-core/oracle/extractor" 11 | "go.uber.org/zap" 12 | ) 13 | 14 | // pulseId uint64, round state.SubRound, tcHeight uint64, intervalId uint64, roundState *RoundState, ctx context.Context 15 | type roundExecuteProps struct { 16 | PulseID uint64 17 | Round state.SubRound 18 | TargetChainHeight uint64 19 | IntervalID uint64 20 | RoundState *RoundState 21 | Ctx context.Context 22 | } 23 | 24 | type roundExecutor struct {} 25 | 26 | 27 | var oracleRoundExecutor = &roundExecutor{} 28 | 29 | 30 | 31 | func (re *roundExecutor) Execute(node *Node, props *roundExecuteProps) error { 32 | pulseId := props.PulseID 33 | round := props.Round 34 | // tcHeight := props.TargetChainHeight 35 | roundState := props.RoundState 36 | ctx := props.Ctx 37 | intervalId := props.IntervalID 38 | 39 | switch round { 40 | case state.CommitSubRound: 41 | zap.L().Sugar().Debugf("Commit subround pulseId: %d", pulseId) 42 | if len(roundState.commitHash) != 0 { 43 | zap.L().Sugar().Debug("Len(commit hash): ", len(roundState.commitHash), roundState.commitHash) 44 | return nil 45 | } 46 | _, err := node.gravityClient.CommitHash(node.chainType, node.nebulaId, int64(intervalId), int64(pulseId), node.oraclePubKey) 47 | if err != nil && err != gravity.ErrValueNotFound { 48 | zap.L().Error(err.Error()) 49 | return err 50 | } else if err == nil { 51 | zap.L().Sugar().Debugf("Commit subround pulseId: %d intervalId: %d exists", pulseId, intervalId) 52 | return nil 53 | } 54 | 55 | data, err := node.extractor.Extract(ctx) 56 | if err != nil && err != extractor.NotFoundErr { 57 | zap.L().Error(err.Error()) 58 | return err 59 | } else if err == extractor.NotFoundErr { 60 | return nil 61 | } 62 | 63 | if data == nil { 64 | zap.L().Debug("Commit subround Extractor Data is empty") 65 | return nil 66 | } 67 | zap.L().Sugar().Debug("Extracted data ", data) 68 | 69 | commit, err := node.invokeCommitTx(data, intervalId, pulseId) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | roundState.commitHash = commit 75 | roundState.data = data 76 | zap.L().Sugar().Debug("Commit round end ", roundState) 77 | case state.RevealSubRound: 78 | zap.L().Debug("Reveal subround") 79 | if len(roundState.commitHash) == 0 || roundState.RevealExist { 80 | zap.L().Sugar().Debugf("CommitHash is empty: %t, RevealExist: %t", len(roundState.commitHash) == 0, roundState.RevealExist) 81 | return nil 82 | } 83 | _, err := node.gravityClient.Reveal(node.chainType, node.oraclePubKey, node.nebulaId, int64(intervalId), int64(pulseId), roundState.commitHash) 84 | if err != nil && err != gravity.ErrValueNotFound { 85 | zap.L().Error(err.Error()) 86 | return err 87 | } else if err == nil { 88 | return nil 89 | } 90 | 91 | err = node.invokeRevealTx(intervalId, pulseId, roundState.data, roundState.commitHash) 92 | if err != nil { 93 | zap.L().Error(err.Error()) 94 | return err 95 | } 96 | roundState.RevealExist = true 97 | zap.L().Sugar().Debug("Reveal round end ", roundState) 98 | case state.ResultSubRound: 99 | zap.L().Debug("Result subround") 100 | if roundState.data == nil && !roundState.RevealExist { 101 | return nil 102 | } 103 | if roundState.resultValue != nil { 104 | zap.L().Debug("Round sign exists") 105 | return nil 106 | } 107 | value, hash, err := node.signRoundResult(intervalId, pulseId, ctx) 108 | if err != nil { 109 | zap.L().Error(err.Error()) 110 | return err 111 | } 112 | //TODO migrate to err 113 | if value == nil { 114 | zap.L().Sugar().Debugf("Value is nil: %t", value == nil) 115 | return nil 116 | } 117 | 118 | roundState.resultValue = value 119 | roundState.resultHash = hash 120 | case state.SendToTargetChain: 121 | zap.L().Debug("Send to target chain subround") 122 | var oracles []account.OraclesPubKey 123 | var myRound uint64 124 | 125 | if roundState.isSent || roundState.resultValue == nil { 126 | zap.L().Sugar().Debugf("roundState.isSent: %t, resultValue is nil: %t", roundState.isSent, roundState.resultValue == nil) 127 | return nil 128 | } 129 | 130 | oraclesMap, err := node.gravityClient.BftOraclesByNebula(node.chainType, node.nebulaId) 131 | if err != nil { 132 | zap.L().Sugar().Debugf("BFT error: %s , \n %s", err, zap.Stack("trace").String) 133 | return nil 134 | } 135 | if _, ok := oraclesMap[node.oraclePubKey.ToString(node.chainType)]; !ok { 136 | zap.L().Debug("Oracle not found") 137 | return nil 138 | } 139 | 140 | var count uint64 141 | for k, v := range oraclesMap { 142 | oracle, err := account.StringToOraclePubKey(k, v) 143 | if err != nil { 144 | return err 145 | } 146 | oracles = append(oracles, oracle) 147 | if node.oraclePubKey == oracle { 148 | myRound = count 149 | } 150 | count++ 151 | } 152 | 153 | if len(oracles) == 0 { 154 | zap.L().Debug("Oracles map is empty") 155 | return nil 156 | } 157 | if intervalId%uint64(len(oracles)) != myRound { 158 | zap.L().Debug("Len oracles != myRound") 159 | return nil 160 | } 161 | zap.L().Sugar().Debugf("Adding pulse id: %d", pulseId) 162 | 163 | txId, err := node.adaptor.AddPulse(node.nebulaId, pulseId, oracles, roundState.resultHash, ctx) 164 | 165 | if err != nil { 166 | zap.L().Sugar().Debugf("Error: %s", err) 167 | return err 168 | } 169 | 170 | if txId != "" { 171 | err = node.adaptor.WaitTx(txId, ctx) 172 | if err != nil { 173 | zap.L().Sugar().Debugf("Error: %s", err) 174 | return err 175 | } 176 | 177 | zap.L().Sugar().Infof("Result tx id: %s", txId) 178 | 179 | roundState.isSent = true 180 | zap.L().Sugar().Debugf("Sending Value to subs, pulse id: %d", pulseId) 181 | err = node.adaptor.SendValueToSubs(node.nebulaId, pulseId, roundState.resultValue, ctx) 182 | if err != nil { 183 | zap.L().Sugar().Debugf("Error: %s", err) 184 | return err 185 | } 186 | } else { 187 | fmt.Printf("Info: Tx result not sent") 188 | } 189 | } 190 | 191 | return nil 192 | } -------------------------------------------------------------------------------- /oracle/node/models.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import "github.com/Gravity-Tech/gravity-core/oracle/extractor" 4 | 5 | type RoundState struct { 6 | data *extractor.Data 7 | commitHash []byte 8 | resultValue *extractor.Data 9 | resultHash []byte 10 | isSent bool 11 | RevealExist bool 12 | commitSent bool 13 | } 14 | -------------------------------------------------------------------------------- /oracle/node/pulse.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "fmt" 7 | 8 | "github.com/Gravity-Tech/gravity-core/oracle/extractor" 9 | "go.uber.org/zap" 10 | 11 | "github.com/Gravity-Tech/gravity-core/common/hashing" 12 | "github.com/Gravity-Tech/gravity-core/common/transactions" 13 | "github.com/ethereum/go-ethereum/common/hexutil" 14 | ) 15 | 16 | func (node *Node) invokeCommitTx(data *extractor.Data, tcHeight uint64, pulseId uint64) ([]byte, error) { 17 | dataBytes := toBytes(data, node.extractor.ExtractorType) 18 | zap.L().Sugar().Debugf("Extractor data type: %d", node.extractor.ExtractorType) 19 | 20 | commit := hashing.WrappedKeccak256(dataBytes, node.chainType) 21 | fmt.Printf("Commit: %s - %s \n", hexutil.Encode(dataBytes), hexutil.Encode(commit[:])) 22 | 23 | tx, err := transactions.New(node.validator.pubKey, transactions.Commit, node.validator.privKey) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | tx.AddValues([]transactions.Value{ 29 | transactions.BytesValue{ 30 | Value: node.nebulaId[:], 31 | }, 32 | transactions.IntValue{ 33 | Value: int64(pulseId), 34 | }, 35 | transactions.IntValue{ 36 | Value: int64(tcHeight), 37 | }, 38 | transactions.BytesValue{ 39 | Value: commit, 40 | }, 41 | transactions.BytesValue{ 42 | Value: node.oraclePubKey[:], 43 | }, 44 | }) 45 | 46 | err = node.gravityClient.SendTx(tx) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | fmt.Printf("Commit txId: %s\n", hexutil.Encode(tx.Id[:])) 52 | 53 | return commit, nil 54 | } 55 | 56 | func (node *Node) invokeRevealTx(tcHeight uint64, pulseId uint64, reveal *extractor.Data, commit []byte) error { 57 | dataBytes := toBytes(reveal, node.extractor.ExtractorType) 58 | fmt.Printf("Reveal: %s - %s \n", hexutil.Encode(dataBytes), hexutil.Encode(commit)) 59 | println(base64.StdEncoding.EncodeToString(dataBytes)) 60 | tx, err := transactions.New(node.validator.pubKey, transactions.Reveal, node.validator.privKey) 61 | if err != nil { 62 | return err 63 | } 64 | tx.AddValues([]transactions.Value{ 65 | transactions.BytesValue{ 66 | Value: commit, 67 | }, 68 | transactions.BytesValue{ 69 | Value: node.nebulaId[:], 70 | }, 71 | transactions.IntValue{ 72 | Value: int64(pulseId), 73 | }, 74 | transactions.IntValue{ 75 | Value: int64(tcHeight), 76 | }, 77 | transactions.BytesValue{ 78 | Value: dataBytes, 79 | }, 80 | transactions.BytesValue{ 81 | Value: node.oraclePubKey[:], 82 | }, 83 | transactions.IntValue{ 84 | Value: int64(node.chainType), 85 | }, 86 | }) 87 | 88 | err = node.gravityClient.SendTx(tx) 89 | if err != nil { 90 | return err 91 | } 92 | fmt.Printf("Reveal txId: %s\n", hexutil.Encode(tx.Id[:])) 93 | 94 | return nil 95 | } 96 | 97 | func (node *Node) signRoundResult(intervalId uint64, pulseId uint64, ctx context.Context) (*extractor.Data, []byte, error) { 98 | zap.L().Sugar().Debugf("signResults: interval: %d, pulseId: %d", intervalId, pulseId) 99 | var values []extractor.Data 100 | zap.L().Sugar().Debugf("gravity Reveals: chaintype: %d, pulseId: %d NebulaId: %s", node.chainType, pulseId, node.nebulaId.ToString(node.chainType)) 101 | bytesValues, err := node.gravityClient.Reveals(node.chainType, node.nebulaId, int64(intervalId), int64(pulseId)) 102 | if err != nil { 103 | return nil, nil, err 104 | } 105 | zap.L().Sugar().Debugf("signResults: value len: %d", len(bytesValues)) 106 | for _, v := range bytesValues { 107 | zap.L().Sugar().Debugf("signResults: decoding: %s", v) 108 | b, err := base64.StdEncoding.DecodeString(v) 109 | if err != nil { 110 | zap.L().Sugar().Error(err.Error()) 111 | continue 112 | } 113 | values = append(values, *fromBytes(b, node.extractor.ExtractorType)) 114 | } 115 | zap.L().Sugar().Debug("Decoded values: ", values) 116 | if len(values) == 0 { 117 | return nil, nil, nil 118 | } 119 | result, err := node.extractor.Aggregate(values, ctx) 120 | if err != nil { 121 | zap.L().Error(err.Error()) 122 | return nil, nil, err 123 | } 124 | 125 | hash := hashing.WrappedKeccak256(toBytes(result, node.extractor.ExtractorType), node.chainType) 126 | 127 | sign, err := node.adaptor.SignHash(node.nebulaId, intervalId, pulseId, hash) 128 | if err != nil { 129 | zap.L().Error(err.Error()) 130 | return nil, nil, err 131 | } 132 | zap.L().Sugar().Infof("Result hash: %s \n", hexutil.Encode(hash)) 133 | 134 | tx, err := transactions.New(node.validator.pubKey, transactions.Result, node.validator.privKey) 135 | if err != nil { 136 | return nil, nil, err 137 | } 138 | tx.AddValues([]transactions.Value{ 139 | transactions.BytesValue{ 140 | Value: node.nebulaId[:], 141 | }, 142 | transactions.IntValue{ 143 | Value: int64(pulseId), 144 | }, 145 | transactions.BytesValue{ 146 | Value: sign, 147 | }, 148 | transactions.BytesValue{ 149 | Value: []byte{byte(node.chainType)}, 150 | }, 151 | transactions.BytesValue{ 152 | Value: node.oraclePubKey[:], 153 | }, 154 | transactions.IntValue{ 155 | Value: int64(node.chainType), 156 | }, 157 | }) 158 | 159 | err = node.gravityClient.SendTx(tx) 160 | if err != nil { 161 | return nil, nil, err 162 | } 163 | 164 | zap.L().Sugar().Infof("Sign result txId: %s\n", hexutil.Encode(tx.Id[:])) 165 | return result, hash, nil 166 | } 167 | -------------------------------------------------------------------------------- /oracle/node/utils.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/binary" 6 | "github.com/Gravity-Tech/gravity-core/abi" 7 | "github.com/Gravity-Tech/gravity-core/oracle/extractor" 8 | "strconv" 9 | ) 10 | 11 | func toBytes(data *extractor.Data, dataType abi.ExtractorType) []byte { 12 | switch dataType { 13 | case abi.Int64Type: 14 | v, _ := strconv.ParseInt(data.Value, 10,64) 15 | var b [8]byte 16 | binary.BigEndian.PutUint64(b[:], uint64(v)) 17 | return b[:] 18 | case abi.StringType: 19 | return []byte(data.Value) 20 | case abi.BytesType: 21 | b, _ := base64.StdEncoding.DecodeString(data.Value) 22 | return b 23 | } 24 | return nil 25 | } 26 | 27 | func fromBytes(value []byte, extractorType abi.ExtractorType) *extractor.Data { 28 | switch extractorType { 29 | case abi.Int64Type: 30 | v := binary.BigEndian.Uint64(value) 31 | return &extractor.Data{ 32 | Type: extractor.Int64, 33 | Value: strconv.FormatInt(int64(v), 10), 34 | } 35 | case abi.StringType: 36 | return &extractor.Data{ 37 | Type: extractor.String, 38 | Value: string(value), 39 | } 40 | case abi.BytesType: 41 | return &extractor.Data{ 42 | Type: extractor.Base64, 43 | Value: base64.StdEncoding.EncodeToString(value), 44 | } 45 | } 46 | 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gravity-core", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "https://github.com/Graviton-One/gravity-core", 6 | "devDependencies": { 7 | "@nomiclabs/hardhat-ethers": "^2.0.2", 8 | "@nomiclabs/hardhat-waffle": "^2.0.1", 9 | "@types/chai": "^4.2.18", 10 | "@types/mocha": "^8.2.2", 11 | "@types/node": "^15.0.2", 12 | "chai": "^4.3.4", 13 | "ethereum-waffle": "^3.3.0", 14 | "ethers": "^5.1.4", 15 | "hardhat": "^2.2.1", 16 | "solidity-coverage": "^0.7.16", 17 | "ts-node": "^10.0.0", 18 | "typescript": "^4.2.4" 19 | }, 20 | "dependencies": { 21 | "@typechain/ethers-v5": "^7.0.1", 22 | "@typechain/hardhat": "^2.0.2", 23 | "@types/chai-as-promised": "^7.1.4", 24 | "chai-as-promised": "^7.1.1", 25 | "typechain": "^5.0.0" 26 | }, 27 | "scripts": { 28 | "compile": "hardhat compile", 29 | "test": "hardhat test", 30 | "coverage": "hardhat coverage" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /rpc/config.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "github.com/Gravity-Tech/gravity-core/common/account" 5 | "github.com/Gravity-Tech/gravity-core/common/gravity" 6 | "github.com/tendermint/tendermint/crypto" 7 | ) 8 | 9 | var GlobalClient *gravity.Client 10 | 11 | type Config struct { 12 | Host string 13 | pubKey account.ConsulPubKey 14 | privKey crypto.PrivKey 15 | client *gravity.Client 16 | } 17 | 18 | func NewConfig(host string, ghClientUrl string, privKey crypto.PrivKey) (*Config, error) { 19 | var ghPubKey account.ConsulPubKey 20 | copy(ghPubKey[:], privKey.PubKey().Bytes()[5:]) 21 | 22 | ghClient, err := gravity.New(ghClientUrl) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return &Config{ 27 | Host: host, 28 | privKey: privKey, 29 | pubKey: ghPubKey, 30 | client: ghClient, 31 | }, nil 32 | } 33 | 34 | func NewGlobalClient(ghClientUrl string) error { 35 | ghClient, err := gravity.New(ghClientUrl) 36 | if err != nil { 37 | return err 38 | } 39 | GlobalClient = ghClient 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /test/shared/expect.ts: -------------------------------------------------------------------------------- 1 | import { expect, use } from "chai" 2 | import { solidity } from "ethereum-waffle" 3 | 4 | use(solidity) 5 | 6 | export { expect } 7 | -------------------------------------------------------------------------------- /test/shared/fixtures.ts: -------------------------------------------------------------------------------- 1 | import { ethers, waffle } from "hardhat" 2 | import { BigNumber } from "ethers" 3 | 4 | import { Gravity } from "../../typechain/Gravity" 5 | import { TestNebula } from "../../typechain/TestNebula" 6 | import { SubMockBytes } from "../../typechain/SubMockBytes" 7 | import { SubMockInt } from "../../typechain/SubMockInt" 8 | import { SubMockString } from "../../typechain/SubMockString" 9 | 10 | import { Fixture } from "ethereum-waffle" 11 | 12 | interface GravityFixture { 13 | gravity: Gravity 14 | } 15 | 16 | export const gravityFixture: Fixture = 17 | async function ( 18 | [wallet, other, consul1, consul2, consul3], 19 | provider 20 | ): Promise { 21 | const gravityFactory = await ethers.getContractFactory("Gravity") 22 | const gravity = (await gravityFactory.deploy( 23 | [consul1.address, 24 | consul2.address, 25 | consul3.address], 26 | 3 27 | )) as Gravity 28 | return { gravity } 29 | } 30 | 31 | interface TestNebulaFixture extends GravityFixture { 32 | nebula: TestNebula 33 | subMockBytes: SubMockBytes 34 | subMockString: SubMockString 35 | subMockInt: SubMockInt 36 | } 37 | 38 | export const testNebulaFixture: Fixture = 39 | async function ( 40 | [wallet, other, consul1, consul2, consul3], 41 | provider 42 | ): Promise { 43 | const { gravity } = await gravityFixture( 44 | [wallet, other, consul1, consul2, consul3], 45 | provider 46 | ) 47 | 48 | const queueFactory = await ethers.getContractFactory("QueueLib") 49 | const queue = await queueFactory.deploy() 50 | 51 | const nebulaFactory = await ethers.getContractFactory("TestNebula", { 52 | libraries: { 53 | QueueLib: queue.address, 54 | }, 55 | }) 56 | const nebula = (await nebulaFactory.deploy( 57 | 2, 58 | gravity.address, 59 | [consul1.address, 60 | consul2.address, 61 | consul3.address], 62 | 3 63 | )) as TestNebula 64 | 65 | const subMockBytesFactory = await ethers.getContractFactory("SubMockBytes") 66 | const subMockBytes = await subMockBytesFactory.deploy(nebula.address, 0) 67 | 68 | const subMockStringFactory = await ethers.getContractFactory("SubMockString") 69 | const subMockString = await subMockStringFactory.deploy(nebula.address, 0) 70 | 71 | const subMockIntFactory = await ethers.getContractFactory("SubMockInt") 72 | const subMockInt = await subMockIntFactory.deploy(nebula.address, 0) 73 | 74 | return { gravity, nebula, subMockBytes, subMockString, subMockInt } 75 | } 76 | -------------------------------------------------------------------------------- /tests/waves/helper.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "github.com/Gravity-Tech/gravity-core/common/helpers" 7 | "github.com/mr-tron/base58" 8 | wavesplatform "github.com/wavesplatform/go-lib-crypto" 9 | "github.com/wavesplatform/gowaves/pkg/client" 10 | "time" 11 | 12 | //"encoding/json" 13 | //"errors" 14 | "github.com/wavesplatform/gowaves/pkg/proto" 15 | "io/ioutil" 16 | 17 | "github.com/wavesplatform/gowaves/pkg/crypto" 18 | ) 19 | 20 | type Account struct { 21 | Address string 22 | // In case of waves: Secret is private key actually 23 | Secret crypto.SecretKey 24 | PubKey crypto.PublicKey 25 | } 26 | 27 | func GenerateAccountFromSeed(chainId byte, wordList string) (*Account, error) { 28 | seed := wavesplatform.Seed(wordList) 29 | wCrypto := wavesplatform.NewWavesCrypto() 30 | address := string(wCrypto.AddressFromSeed(seed, wavesplatform.WavesChainID(chainId))) 31 | seedWaves, err := crypto.NewSecretKeyFromBase58(string(wCrypto.PrivateKey(seed))) 32 | if err != nil { 33 | return nil, err 34 | } 35 | pubKey := wCrypto.PublicKey(seed) 36 | 37 | return &Account{ 38 | Address: address, 39 | PubKey: crypto.PublicKey(crypto.MustDigestFromBase58(string(pubKey))), 40 | Secret: seedWaves, 41 | }, nil 42 | } 43 | 44 | func ScriptFromFile(filename string) ([]byte, error) { 45 | scriptBytes, err := ioutil.ReadFile(filename) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | script, err := base64.StdEncoding.DecodeString(string(scriptBytes)) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | return script, nil 56 | } 57 | 58 | type NetworkEnvironment int 59 | 60 | const ( 61 | Wavelet = 1e8 62 | ) 63 | 64 | const ( 65 | WavesStagenet NetworkEnvironment = iota 66 | WavesTestnet 67 | ) 68 | 69 | func (env NetworkEnvironment) NodeURL() string { 70 | switch env { 71 | case WavesStagenet: 72 | return "https://nodes-stagenet.wavesnodes.com" 73 | } 74 | 75 | panic("no node url") 76 | } 77 | 78 | func (env NetworkEnvironment) ChainIDBytes() byte { 79 | return env.ChainID()[0] 80 | } 81 | 82 | func (env NetworkEnvironment) ChainID() string { 83 | switch env { 84 | case WavesStagenet: 85 | return "S" 86 | } 87 | 88 | panic("invalid chain id") 89 | } 90 | 91 | type WavesTestConfig struct { 92 | ctx context.Context 93 | DistributorSeed string 94 | Environment NetworkEnvironment 95 | } 96 | 97 | type WavesActor wavesplatform.Seed 98 | 99 | func NewWavesActor() WavesActor { 100 | wCrypto := wavesplatform.NewWavesCrypto() 101 | 102 | return WavesActor(wCrypto.RandomSeed()) 103 | } 104 | 105 | func (actor WavesActor) Account(chainId byte) *Account { 106 | account, _ := GenerateAccountFromSeed(chainId, string(actor)) 107 | return account 108 | } 109 | 110 | func (actor WavesActor) Recipient(chainId byte) proto.Recipient { 111 | recipient, _ := proto.NewRecipientFromString(actor.Account(chainId).Address) 112 | return recipient 113 | } 114 | 115 | func (actor WavesActor) SecretKey() crypto.SecretKey { 116 | privKey, _ := crypto.NewSecretKeyFromBase58(string(actor.wcrypto().PrivateKey(wavesplatform.Seed(actor)))) 117 | return privKey 118 | } 119 | 120 | func (actor WavesActor) wcrypto() wavesplatform.WavesCrypto { 121 | return wavesplatform.NewWavesCrypto() 122 | } 123 | 124 | type WavesActorSeedsMock struct { 125 | Gravity, Nebula, Subscriber WavesActor 126 | } 127 | 128 | func NewWavesActorsMock() WavesActorSeedsMock { 129 | return WavesActorSeedsMock{ 130 | Gravity: NewWavesActor(), 131 | Nebula: NewWavesActor(), 132 | Subscriber: NewWavesActor(), 133 | } 134 | } 135 | 136 | func SignAndBroadcast(tx proto.Transaction, cfg WavesTestConfig, clientWaves *client.Client, wavesHelper helpers.ClientHelper, senderSeed crypto.SecretKey) ( 137 | *struct { 138 | Response *client.Response 139 | TxID string 140 | }, error) { 141 | var err error 142 | 143 | result := &struct { 144 | Response *client.Response 145 | TxID string 146 | }{ Response: nil, TxID: "" } 147 | 148 | err = tx.Sign(cfg.Environment.ChainIDBytes(), senderSeed) 149 | if err != nil { 150 | return result, err 151 | } 152 | 153 | var response *client.Response 154 | response, err = clientWaves.Transactions.Broadcast(cfg.ctx, tx) 155 | if err != nil { 156 | return result, err 157 | } 158 | 159 | result.Response = response 160 | 161 | txID, err := tx.GetID(cfg.Environment.ChainIDBytes()) 162 | result.TxID = base58.Encode(txID) 163 | 164 | if err != nil { 165 | return result, err 166 | } 167 | 168 | // just for compile avoidance 169 | err = <-wavesHelper.WaitTx(base58.Encode(txID), cfg.ctx) 170 | if err != nil { 171 | return result, err 172 | } 173 | 174 | return result, nil 175 | } 176 | 177 | func TransferWavesTransaction(senderPubKey crypto.PublicKey, amount uint64, recipient proto.Recipient) *proto.TransferWithSig { 178 | tx := &proto.TransferWithSig{ 179 | Type: proto.TransferTransaction, 180 | Version: 1, 181 | Transfer: proto.Transfer{ 182 | SenderPK: senderPubKey, 183 | Fee: 0.001 * Wavelet, 184 | Timestamp: client.NewTimestampFromTime(time.Now()), 185 | Recipient: recipient, 186 | Amount: amount, 187 | }, 188 | } 189 | 190 | return tx 191 | } 192 | -------------------------------------------------------------------------------- /tests/waves/mock.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "github.com/wavesplatform/gowaves/pkg/proto" 6 | "strings" 7 | ) 8 | 9 | type NebulaTestMockConfig struct { 10 | chainId byte 11 | 12 | BftCoefficient int64 `dtx:"bft_coefficient"` 13 | GravityAddress string `dtx:"gravity_contract"` 14 | NebulaPubkey string `dtx:"contract_pubkey"` 15 | SubscriberAddress string `dtx:"subscriber_address"` 16 | OraclesList [5]WavesActor `dtx:"oracles"` 17 | } 18 | 19 | func (nebulaMockCfg *NebulaTestMockConfig) Validate() error { 20 | if nebulaMockCfg.BftCoefficient < 1 { 21 | return fmt.Errorf("bft_coefficient is less than 1") 22 | } 23 | 24 | if nebulaMockCfg.chainId != 'S' { 25 | return fmt.Errorf("only waves stagenet is supported") 26 | } 27 | 28 | if nebulaMockCfg.GravityAddress == "" { 29 | return fmt.Errorf("field \"gravity_contract\" cannot be empty") 30 | } 31 | if nebulaMockCfg.NebulaPubkey == "" { 32 | return fmt.Errorf("field \"contract_pubkey\" cannot be empty") 33 | } 34 | if nebulaMockCfg.SubscriberAddress == "" { 35 | return fmt.Errorf("field \"subscriber_address\" cannot be empty") 36 | } 37 | 38 | return nil 39 | } 40 | 41 | func (nebulaMockCfg *NebulaTestMockConfig) OraclesPubKeysListDataEntry () string { 42 | var res []string 43 | 44 | for _, oracle := range nebulaMockCfg.OraclesList { 45 | res = append(res, oracle.Account(nebulaMockCfg.chainId).PubKey.String()) 46 | } 47 | 48 | return strings.Join(res, ",") 49 | } 50 | 51 | func (nebulaMockCfg *NebulaTestMockConfig) DataEntries () proto.DataEntries { 52 | return proto.DataEntries{ 53 | &proto.IntegerDataEntry{ 54 | Key: "bft_coefficient", 55 | Value: nebulaMockCfg.BftCoefficient, 56 | }, 57 | &proto.StringDataEntry{ 58 | Key: "gravity_contract", 59 | Value: nebulaMockCfg.GravityAddress, 60 | }, 61 | &proto.StringDataEntry{ 62 | Key: "contract_pubkey", 63 | Value: nebulaMockCfg.NebulaPubkey, 64 | }, 65 | &proto.StringDataEntry{ 66 | Key: "subscriber_address", 67 | Value: nebulaMockCfg.SubscriberAddress, 68 | }, 69 | &proto.StringDataEntry{ 70 | Key: "oracles", 71 | Value: nebulaMockCfg.OraclesPubKeysListDataEntry(), 72 | }, 73 | } 74 | } 75 | 76 | func (nebulaMockCfg *NebulaTestMockConfig) OraclesPubKeysList () []string { 77 | var oraclesPubKeyList []string 78 | 79 | for _, mockedConsul := range nebulaMockCfg.OraclesList { 80 | pk := mockedConsul.Account(cfg.Environment.ChainIDBytes()).PubKey.String() 81 | oraclesPubKeyList = append(oraclesPubKeyList, pk) 82 | } 83 | 84 | return oraclesPubKeyList 85 | } 86 | --------------------------------------------------------------------------------