diff --git a/CODEOWNERS b/CODEOWNERS index 74a7208cc0a..b6052f1628a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -66,6 +66,8 @@ core/scripts/gateway @smartcontractkit/functions /contracts/**/*llo-feeds* @smartcontrackit/mercury-team /contracts/**/*vrf* @smartcontractkit/vrf-team /contracts/**/*l2ep* @smartcontractkit/integrations +# TODO: replace with a team tag when ready +/contracts/**/*keystone* @archseer @bolekk @patrick-dowell /contracts/src/v0.8/automation @smartcontractkit/keepers /contracts/src/v0.8/functions @smartcontractkit/functions diff --git a/contracts/src/v0.8/keystone/OCR3Capability.sol b/contracts/src/v0.8/keystone/OCR3Capability.sol new file mode 100644 index 00000000000..872ff7e910e --- /dev/null +++ b/contracts/src/v0.8/keystone/OCR3Capability.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.6; + +import {OCR2Base} from "../shared/ocr2/OCR2Base.sol"; + +// OCR2Base provides config management compatible with OCR3 +contract OCR3Capability is OCR2Base { + error ReportingUnsupported(); + + constructor() OCR2Base(true) {} + + function typeAndVersion() external pure override returns (string memory) { + return "Keystone 0.0.0"; + } + + function _beforeSetConfig(uint8 _f, bytes memory _onchainConfig) internal override {} + + function _afterSetConfig(uint8 _f, bytes memory _onchainConfig) internal override {} + + function _validateReport( + bytes32 /* configDigest */, + uint40 /* epochAndRound */, + bytes memory /* report */ + ) internal pure override returns (bool) { + return true; + } + + function _report( + uint256 /* initialGas */, + address /* transmitter */, + uint8 /* signerCount */, + address[MAX_NUM_ORACLES] memory /* signers */, + bytes calldata /* report */ + ) internal virtual override { + revert ReportingUnsupported(); + } +} diff --git a/core/scripts/keystone/config_example.json b/core/scripts/keystone/config_example.json new file mode 100644 index 00000000000..6835a4143f4 --- /dev/null +++ b/core/scripts/keystone/config_example.json @@ -0,0 +1,27 @@ +{ + "OracleConfig": { + "MaxQueryLengthBytes": 1000000, + "MaxObservationLengthBytes": 1000000, + "MaxReportLengthBytes": 1000000, + "MaxRequestBatchSize": 1000, + "UniqueReports": true, + + "DeltaProgressMillis": 5000, + "DeltaResendMillis": 5000, + "DeltaInitialMillis": 5000, + "DeltaRoundMillis": 2000, + "DeltaGraceMillis": 500, + "DeltaCertifiedCommitRequestMillis": 1000, + "DeltaStageMillis": 30000, + "MaxRoundsPerEpoch": 10, + "TransmissionSchedule": [1, 1, 1, 1], + + "MaxDurationQueryMillis": 1000, + "MaxDurationObservationMillis": 1000, + "MaxDurationReportMillis": 1000, + "MaxDurationAcceptMillis": 1000, + "MaxDurationTransmitMillis": 1000, + + "MaxFaultyOracles": 1 + } +} diff --git a/core/scripts/keystone/gen_ocr3_config.go b/core/scripts/keystone/gen_ocr3_config.go new file mode 100644 index 00000000000..2f873dadda7 --- /dev/null +++ b/core/scripts/keystone/gen_ocr3_config.go @@ -0,0 +1,207 @@ +package main + +import ( + "crypto/ed25519" + "encoding/hex" + "encoding/json" + "flag" + "fmt" + "io" + "os" + "time" + + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + + helpers "github.com/smartcontractkit/chainlink/core/scripts/common" +) + +type TopLevelConfigSource struct { + OracleConfig OracleConfigSource +} + +type OracleConfigSource struct { + MaxQueryLengthBytes uint32 + MaxObservationLengthBytes uint32 + MaxReportLengthBytes uint32 + MaxRequestBatchSize uint32 + UniqueReports bool + + DeltaProgressMillis uint32 + DeltaResendMillis uint32 + DeltaInitialMillis uint32 + DeltaRoundMillis uint32 + DeltaGraceMillis uint32 + DeltaCertifiedCommitRequestMillis uint32 + DeltaStageMillis uint32 + MaxRoundsPerEpoch uint64 + TransmissionSchedule []int + + MaxDurationQueryMillis uint32 + MaxDurationObservationMillis uint32 + MaxDurationAcceptMillis uint32 + MaxDurationTransmitMillis uint32 + + MaxFaultyOracles int +} + +type NodeKeys struct { + EthAddress string + P2PPeerID string // p2p_ + OCR2BundleID string // used only in job spec + OCR2OnchainPublicKey string // ocr2on_evm_ + OCR2OffchainPublicKey string // ocr2off_evm_ + OCR2ConfigPublicKey string // ocr2cfg_evm_ + CSAPublicKey string +} + +type orc2drOracleConfig struct { + Signers []string `json:"signers"` + Transmitters []string `json:"transmitters"` + F uint8 `json:"f"` + OnchainConfig string `json:"onchainConfig"` + OffchainConfigVersion uint64 `json:"offchainConfigVersion"` + OffchainConfig string `json:"offchainConfig"` +} + +type generateOCR2Config struct { +} + +func NewGenerateOCR2ConfigCommand() *generateOCR2Config { + return &generateOCR2Config{} +} + +func (g *generateOCR2Config) Name() string { + return "generate-ocr2config" +} + +func mustParseJSONConfigFile(fileName string) (output TopLevelConfigSource) { + return mustParseJSON[TopLevelConfigSource](fileName) +} + +func mustParseKeysFile(fileName string) (output []NodeKeys) { + return mustParseJSON[[]NodeKeys](fileName) +} + +func mustParseJSON[T any](fileName string) (output T) { + jsonFile, err := os.Open(fileName) + if err != nil { + panic(err) + } + defer jsonFile.Close() + bytes, err := io.ReadAll(jsonFile) + if err != nil { + panic(err) + } + err = json.Unmarshal(bytes, &output) + if err != nil { + panic(err) + } + return +} + +func main() { + fs := flag.NewFlagSet("config_gen", flag.ExitOnError) + configFile := fs.String("config", "config_example.json", "a file containing JSON config") + keysFile := fs.String("keys", "public_keys_example.json", "a file containing node public keys") + if err := fs.Parse(os.Args[1:]); err != nil || *keysFile == "" || *configFile == "" { + fs.Usage() + os.Exit(1) + } + + topLevelCfg := mustParseJSONConfigFile(*configFile) + cfg := topLevelCfg.OracleConfig + nca := mustParseKeysFile(*keysFile) + + onchainPubKeys := []common.Address{} + for _, n := range nca { + onchainPubKeys = append(onchainPubKeys, common.HexToAddress(n.OCR2OnchainPublicKey)) + } + + offchainPubKeysBytes := []types.OffchainPublicKey{} + for _, n := range nca { + pkBytes, err := hex.DecodeString(n.OCR2OffchainPublicKey) + if err != nil { + panic(err) + } + + pkBytesFixed := [ed25519.PublicKeySize]byte{} + nCopied := copy(pkBytesFixed[:], pkBytes) + if nCopied != ed25519.PublicKeySize { + panic("wrong num elements copied from ocr2 offchain public key") + } + + offchainPubKeysBytes = append(offchainPubKeysBytes, types.OffchainPublicKey(pkBytesFixed)) + } + + configPubKeysBytes := []types.ConfigEncryptionPublicKey{} + for _, n := range nca { + pkBytes, err := hex.DecodeString(n.OCR2ConfigPublicKey) + helpers.PanicErr(err) + + pkBytesFixed := [ed25519.PublicKeySize]byte{} + n := copy(pkBytesFixed[:], pkBytes) + if n != ed25519.PublicKeySize { + panic("wrong num elements copied") + } + + configPubKeysBytes = append(configPubKeysBytes, types.ConfigEncryptionPublicKey(pkBytesFixed)) + } + + identities := []confighelper.OracleIdentityExtra{} + for index := range nca { + identities = append(identities, confighelper.OracleIdentityExtra{ + OracleIdentity: confighelper.OracleIdentity{ + OnchainPublicKey: onchainPubKeys[index][:], + OffchainPublicKey: offchainPubKeysBytes[index], + PeerID: nca[index].P2PPeerID, + TransmitAccount: types.Account(nca[index].EthAddress), + }, + ConfigEncryptionPublicKey: configPubKeysBytes[index], + }) + } + + signers, transmitters, f, onchainConfig, offchainConfigVersion, offchainConfig, err := ocr3confighelper.ContractSetConfigArgsForTests( + time.Duration(cfg.DeltaProgressMillis)*time.Millisecond, + time.Duration(cfg.DeltaResendMillis)*time.Millisecond, + time.Duration(cfg.DeltaInitialMillis)*time.Millisecond, + time.Duration(cfg.DeltaRoundMillis)*time.Millisecond, + time.Duration(cfg.DeltaGraceMillis)*time.Millisecond, + time.Duration(cfg.DeltaCertifiedCommitRequestMillis)*time.Millisecond, + time.Duration(cfg.DeltaStageMillis)*time.Millisecond, + cfg.MaxRoundsPerEpoch, + cfg.TransmissionSchedule, + identities, + nil, // empty plugin config + time.Duration(cfg.MaxDurationQueryMillis)*time.Millisecond, + time.Duration(cfg.MaxDurationObservationMillis)*time.Millisecond, + time.Duration(cfg.MaxDurationAcceptMillis)*time.Millisecond, + time.Duration(cfg.MaxDurationTransmitMillis)*time.Millisecond, + cfg.MaxFaultyOracles, + nil, // empty onChain config + ) + helpers.PanicErr(err) + + var signersStr []string + var transmittersStr []string + for i := range transmitters { + signersStr = append(signersStr, "0x"+hex.EncodeToString(signers[i])) + transmittersStr = append(transmittersStr, string(transmitters[i])) + } + + config := orc2drOracleConfig{ + Signers: signersStr, + Transmitters: transmittersStr, + F: f, + OnchainConfig: "0x" + hex.EncodeToString(onchainConfig), + OffchainConfigVersion: offchainConfigVersion, + OffchainConfig: "0x" + hex.EncodeToString(offchainConfig), + } + + js, err := json.MarshalIndent(config, "", " ") + helpers.PanicErr(err) + fmt.Println("Config:", string(js)) +} diff --git a/core/scripts/keystone/public_keys_example.json b/core/scripts/keystone/public_keys_example.json new file mode 100644 index 00000000000..1baae3d5c9b --- /dev/null +++ b/core/scripts/keystone/public_keys_example.json @@ -0,0 +1,38 @@ +[ + { + "EthAddress": "0x9639dCc7D0ca4468B5f684ef89F12F0B365c9F6d", + "P2PPeerID": "12D3KooWBCF1XT5Wi8FzfgNCqRL76Swv8TRU3TiD4QiJm8NMNX7N", + "OCR2BundleID": "29e8dd8c916eac1fc6df8ac6f449481f5db1cf3714e7cb629e16d7b499af5603", + "OCR2OnchainPublicKey": "049b55674e2485b9a6788c0796b74f2c33667670", + "OCR2OffchainPublicKey": "f4f13ca3053e0f44f869648857f0c0ebc20045f9b51d6a5712c867f5920def01", + "OCR2ConfigPublicKey": "17a60b694f7cfb0a02da3da5e79f87b071e518d1f4a27aeb181776969b766e1e", + "CSAPublicKey": "csa_2245bbbee39f75f22e12c345a0ac76bad1f3214b7020a47324a1634dee285761" + }, + { + "EthAddress": "0x8f0fAE64f5f75067833ed5deDC2804B62b21383d", + "P2PPeerID": "12D3KooWG1AyvwmCpZ93J8pBQUE1SuzrjDXnT4BeouncHR3jWLCG", + "OCR2BundleID": "9b19ff8196aec2cdf70f6ac2c3617ea9b03dfb426e0938aee0af2b37459f86eb", + "OCR2OnchainPublicKey": "8405c2b28fcab284f08ed0028f1f511cd57a16b7", + "OCR2OffchainPublicKey": "29e41861f005a0b1bc3156ecd40992a5b552b3182f73f4adec43b4e3052ac2c7", + "OCR2ConfigPublicKey": "0a0c21b14924ee28afad15c96b3a5be372870cdf9b13f002b63fb4ba8942c461", + "CSAPublicKey": "csa_d329bfff7b7835aec205a6ac09bb217e46c754c3ab6730d09b709c7bc395dc3f" + }, + { + "EthAddress": "0xf09A863D920840c13277e76F43CFBdfB22b8FB7C", + "P2PPeerID": "12D3KooWGeUKZBRMbx27FUTgBwZa9Ap9Ym92mywwpuqkEtz8XWyv", + "OCR2BundleID": "200e5d187b4fc8e2d263c13cd28d37c914f275aba1aee91199d5c7766ada9d63", + "OCR2OnchainPublicKey": "4a5199a4e65acfd07b690493ec02bdba3c44c1ec", + "OCR2OffchainPublicKey": "f535037b9ca113d61eb0548e62c28d5abc201bfe36a803ea5f1e94f66da64c9e", + "OCR2ConfigPublicKey": "d62623257ebf3e805ee77162a1b1df0d1b3f57c7dc1dc28b88acb6c9b4f5455f", + "CSAPublicKey": "csa_35ff548c85ada4dbd09b5f02ea3421217d15ebec00a63cab7e529f8bb90542a1" + }, + { + "EthAddress": "0x7eD90b519bC3054a575C464dBf39946b53Ff90EF", + "P2PPeerID": "12D3KooW9zYWQv3STmDeNDidyzxsJSTxoCTLicafgfeEz9nhwhC4", + "OCR2BundleID": "25b40149256109e6036f27b11969c61ba9e765fc60a13324410588c4801f0466", + "OCR2OnchainPublicKey": "07183b1fe36c8f3885a3ce4131b9151a2c7e86b4", + "OCR2OffchainPublicKey": "c64252067d48efe341745cff9aad2f20990158dfe31548322a0bf2921e9428b8", + "OCR2ConfigPublicKey": "893f61aa755f356a248d66b58974742b22518db962ab036b440a33c3eb86ea5b", + "CSAPublicKey": "csa_541f97026a2be5e460276ba4eddebce6e74ccb1d1cfb1550fe54ef615c0534c9" + } +]