-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add automation types and services #297
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
9c24f51
Move automation types into common
ferglor c9da586
Remove mocks
ferglor 90323ae
Add mercury credentials
ferglor 15ef2ef
Use services.Service in EventProvider
ferglor 1755c71
goimports
ferglor 306f99b
Reduce the number of automation types needed in common
ferglor File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,333 @@ | ||
package automation | ||
|
||
import ( | ||
"encoding/hex" | ||
"encoding/json" | ||
"fmt" | ||
"math/big" | ||
"strings" | ||
"time" | ||
) | ||
|
||
const ( | ||
checkResultDelimiter = 0x09 | ||
) | ||
|
||
// checkResultStringTemplate is a JSON template, used for debugging purposes only. | ||
var checkResultStringTemplate = `{ | ||
"PipelineExecutionState":%d, | ||
"Retryable":%v, | ||
"Eligible":%v, | ||
"IneligibilityReason":%d, | ||
"UpkeepID":%s, | ||
"Trigger":%s, | ||
"WorkID":"%s", | ||
"GasAllocated":%d, | ||
"PerformData":"%s", | ||
"FastGasWei":%s, | ||
"LinkNative":%s, | ||
"RetryInterval":%d | ||
}` | ||
|
||
func init() { | ||
checkResultStringTemplate = strings.Replace(checkResultStringTemplate, " ", "", -1) | ||
checkResultStringTemplate = strings.Replace(checkResultStringTemplate, "\t", "", -1) | ||
checkResultStringTemplate = strings.Replace(checkResultStringTemplate, "\n", "", -1) | ||
} | ||
|
||
type TransmitEventType int | ||
|
||
const ( | ||
UnknownEvent TransmitEventType = iota | ||
PerformEvent | ||
StaleReportEvent | ||
ReorgReportEvent | ||
InsufficientFundsReportEvent | ||
) | ||
|
||
// UpkeepState is a final state of some unit of work. | ||
type UpkeepState uint8 | ||
|
||
const ( | ||
UnknownState UpkeepState = iota | ||
// Performed means the upkeep was performed | ||
Performed | ||
// Ineligible means the upkeep was not eligible to be performed | ||
Ineligible | ||
) | ||
|
||
// UpkeepIdentifier is a unique identifier for the upkeep, represented as uint256 in the contract. | ||
type UpkeepIdentifier [32]byte | ||
|
||
// String returns a base 10 numerical string representation of the upkeep identifier. | ||
func (u UpkeepIdentifier) String() string { | ||
return u.BigInt().String() | ||
} | ||
|
||
func (u UpkeepIdentifier) BigInt() *big.Int { | ||
return big.NewInt(0).SetBytes(u[:]) | ||
} | ||
|
||
// FromBigInt sets the upkeep identifier from a big.Int, | ||
// returning true if the big.Int is valid and false otherwise. | ||
// in case of an invalid big.Int the upkeep identifier is set to 32 zeros. | ||
func (u *UpkeepIdentifier) FromBigInt(i *big.Int) bool { | ||
*u = [32]byte{} | ||
if i.Cmp(big.NewInt(0)) == -1 { | ||
return false | ||
} | ||
b := i.Bytes() | ||
if len(b) == 0 { | ||
return true | ||
} | ||
if len(b) <= 32 { | ||
copy(u[32-len(b):], i.Bytes()) | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
type BlockNumber uint64 | ||
|
||
// BlockKey represent a block (number and hash) | ||
// NOTE: This struct is sent on the p2p network as part of observations to get quorum | ||
// Any change here should be backwards compatible and should keep validation and | ||
// quorum requirements in mind. Please ensure to get a proper review along with an | ||
// upgrade plan before changing this | ||
type BlockKey struct { | ||
Number BlockNumber | ||
Hash [32]byte | ||
} | ||
|
||
type TransmitEvent struct { | ||
// Type describes the type of event | ||
Type TransmitEventType | ||
// TransmitBlock is the block height of the transmit event | ||
TransmitBlock BlockNumber | ||
// Confirmations is the block height behind latest | ||
Confirmations int64 | ||
// TransactionHash is the hash for the transaction where the event originated | ||
TransactionHash [32]byte | ||
// UpkeepID uniquely identifies the upkeep in the registry | ||
UpkeepID UpkeepIdentifier | ||
// WorkID uniquely identifies the unit of work for the specified upkeep | ||
WorkID string | ||
// CheckBlock is the block value that the upkeep was originally checked at | ||
CheckBlock BlockNumber | ||
} | ||
|
||
// NOTE: This struct is sent on the p2p network as part of observations to get quorum | ||
// Any change here should be backwards compatible and should keep validation and | ||
// quorum requirements in mind. Any field that is needed to be encoded should be added | ||
// as well to checkResultMsg struct, and to be encoded/decoded in the MarshalJSON and | ||
// UnmarshalJSON functions. Please ensure to get a proper review along with an upgrade | ||
// plan before changing this. | ||
type CheckResult struct { | ||
// zero if success, else indicates an error code | ||
PipelineExecutionState uint8 | ||
// if PipelineExecutionState is non zero, then retryable indicates that the same | ||
// payload can be processed again in order to get a successful execution | ||
Retryable bool | ||
// Rest of these fields are only applicable if PipelineExecutionState is zero | ||
// Eligible indicates whether this result is eligible to be performed | ||
Eligible bool | ||
// If result is not eligible then the reason it failed. Should be 0 if eligible | ||
IneligibilityReason uint8 | ||
// Upkeep is all the information that identifies the upkeep | ||
UpkeepID UpkeepIdentifier | ||
// Trigger is the event that triggered the upkeep to be checked | ||
Trigger Trigger | ||
// WorkID represents the unit of work for the check result | ||
// Exploratory: Make workID an internal field and an external WorkID() function which generates WID | ||
WorkID string | ||
// GasAllocated is the gas to provide an upkeep in a report | ||
GasAllocated uint64 | ||
// PerformData is the raw data returned when simulating an upkeep perform | ||
PerformData []byte | ||
// FastGasWei is the fast gas price in wei when performing this upkeep | ||
FastGasWei *big.Int | ||
// Link to native ratio to be used when performing this upkeep | ||
LinkNative *big.Int | ||
// RetryInterval is the time interval after which the same payload can be retried. | ||
// This field is used is special cases (such as mercury lookup), where we want to | ||
// have a different retry interval than the default one (30s) | ||
// NOTE: this field is not encoded in JSON and is only used internally | ||
RetryInterval time.Duration | ||
} | ||
|
||
// checkResultMsg is used for encoding and decoding check results. | ||
type checkResultMsg struct { | ||
PipelineExecutionState uint8 | ||
Retryable bool | ||
Eligible bool | ||
IneligibilityReason uint8 | ||
UpkeepID UpkeepIdentifier | ||
Trigger Trigger | ||
WorkID string | ||
GasAllocated uint64 | ||
PerformData []byte | ||
FastGasWei *big.Int | ||
LinkNative *big.Int | ||
} | ||
|
||
// UniqueID returns a unique identifier for the check result. | ||
// It is used to achieve quorum on results before being sent within a report. | ||
func (r CheckResult) UniqueID() string { | ||
var resultBytes []byte | ||
|
||
resultBytes = append(resultBytes, r.PipelineExecutionState) | ||
resultBytes = append(resultBytes, checkResultDelimiter) | ||
|
||
resultBytes = append(resultBytes, []byte(fmt.Sprintf("%+v", r.Retryable))...) | ||
resultBytes = append(resultBytes, checkResultDelimiter) | ||
|
||
resultBytes = append(resultBytes, []byte(fmt.Sprintf("%+v", r.Eligible))...) | ||
resultBytes = append(resultBytes, checkResultDelimiter) | ||
|
||
resultBytes = append(resultBytes, r.IneligibilityReason) | ||
resultBytes = append(resultBytes, checkResultDelimiter) | ||
|
||
resultBytes = append(resultBytes, r.UpkeepID[:]...) | ||
resultBytes = append(resultBytes, checkResultDelimiter) | ||
|
||
resultBytes = append(resultBytes, r.Trigger.BlockHash[:]...) | ||
resultBytes = append(resultBytes, checkResultDelimiter) | ||
|
||
resultBytes = append(resultBytes, big.NewInt(int64(r.Trigger.BlockNumber)).Bytes()...) | ||
resultBytes = append(resultBytes, checkResultDelimiter) | ||
|
||
if r.Trigger.LogTriggerExtension != nil { | ||
// Note: We encode the whole trigger extension so the behaiour of | ||
// LogTriggerExtentsion.BlockNumber and LogTriggerExtentsion.BlockHash should be | ||
// consistent across nodes when sending observations | ||
resultBytes = append(resultBytes, []byte(fmt.Sprintf("%+v", r.Trigger.LogTriggerExtension))...) | ||
} | ||
resultBytes = append(resultBytes, checkResultDelimiter) | ||
|
||
resultBytes = append(resultBytes, r.WorkID[:]...) | ||
resultBytes = append(resultBytes, checkResultDelimiter) | ||
|
||
resultBytes = append(resultBytes, big.NewInt(int64(r.GasAllocated)).Bytes()...) | ||
resultBytes = append(resultBytes, checkResultDelimiter) | ||
|
||
resultBytes = append(resultBytes, r.PerformData[:]...) | ||
resultBytes = append(resultBytes, checkResultDelimiter) | ||
|
||
if r.FastGasWei != nil { | ||
resultBytes = append(resultBytes, r.FastGasWei.Bytes()...) | ||
} | ||
resultBytes = append(resultBytes, checkResultDelimiter) | ||
|
||
if r.LinkNative != nil { | ||
resultBytes = append(resultBytes, r.LinkNative.Bytes()...) | ||
} | ||
resultBytes = append(resultBytes, checkResultDelimiter) | ||
|
||
return fmt.Sprintf("%x", resultBytes) | ||
} | ||
|
||
// NOTE: this function is used for debugging purposes only. | ||
// for encoding check results, please use the Encoder interface | ||
func (r CheckResult) String() string { | ||
return fmt.Sprintf( | ||
checkResultStringTemplate, r.PipelineExecutionState, r.Retryable, r.Eligible, | ||
r.IneligibilityReason, r.UpkeepID, r.Trigger, r.WorkID, r.GasAllocated, | ||
hex.EncodeToString(r.PerformData), r.FastGasWei, r.LinkNative, r.RetryInterval, | ||
) | ||
} | ||
|
||
func (r CheckResult) MarshalJSON() ([]byte, error) { | ||
crm := &checkResultMsg{ | ||
PipelineExecutionState: r.PipelineExecutionState, | ||
Retryable: r.Retryable, | ||
Eligible: r.Eligible, | ||
IneligibilityReason: r.IneligibilityReason, | ||
UpkeepID: r.UpkeepID, | ||
Trigger: r.Trigger, | ||
WorkID: r.WorkID, | ||
GasAllocated: r.GasAllocated, | ||
PerformData: r.PerformData, | ||
FastGasWei: r.FastGasWei, | ||
LinkNative: r.LinkNative, | ||
} | ||
|
||
return json.Marshal(crm) | ||
} | ||
|
||
func (r *CheckResult) UnmarshalJSON(data []byte) error { | ||
var crm checkResultMsg | ||
|
||
if err := json.Unmarshal(data, &crm); err != nil { | ||
return err | ||
} | ||
|
||
r.PipelineExecutionState = crm.PipelineExecutionState | ||
r.Retryable = crm.Retryable | ||
r.Eligible = crm.Eligible | ||
r.IneligibilityReason = crm.IneligibilityReason | ||
r.UpkeepID = crm.UpkeepID | ||
r.Trigger = crm.Trigger | ||
r.WorkID = crm.WorkID | ||
r.GasAllocated = crm.GasAllocated | ||
r.PerformData = crm.PerformData | ||
r.FastGasWei = crm.FastGasWei | ||
r.LinkNative = crm.LinkNative | ||
|
||
return nil | ||
} | ||
|
||
// BlockHistory is a list of block keys | ||
type BlockHistory []BlockKey | ||
|
||
func (bh BlockHistory) Latest() (BlockKey, error) { | ||
if len(bh) == 0 { | ||
return BlockKey{}, fmt.Errorf("empty block history") | ||
} | ||
|
||
return bh[0], nil | ||
} | ||
|
||
type UpkeepPayload struct { | ||
// Upkeep is all the information that identifies the upkeep | ||
UpkeepID UpkeepIdentifier | ||
// Trigger is the event that triggered the upkeep to be checked | ||
Trigger Trigger | ||
// WorkID uniquely identifies the unit of work for the specified upkeep | ||
WorkID string | ||
// CheckData is the data used to check the upkeep | ||
CheckData []byte | ||
} | ||
|
||
// Determines whether the payload is empty, used within filtering | ||
func (p UpkeepPayload) IsEmpty() bool { | ||
return p.WorkID == "" | ||
} | ||
|
||
// CoordinatedBlockProposal is used to represent a unit of work that can be performed | ||
// after a check block has been coordinated between nodes. | ||
// NOTE: This struct is sent on the p2p network as part of observations to get quorum | ||
// Any change here should be backwards compatible and should keep validation and | ||
// quorum requirements in mind. Please ensure to get a proper review along with an | ||
// upgrade plan before changing this | ||
// NOTE: Only the trigger.BlockHash and trigger.BlockNumber are coordinated across | ||
// the network to get a quorum. WorkID is guaranteed to be correctly generated. | ||
// Rest of the fields here SHOULD NOT BE TRUSTED as they can be manipulated by | ||
// a single malicious node. | ||
type CoordinatedBlockProposal struct { | ||
// UpkeepID is the id of the proposed upkeep | ||
UpkeepID UpkeepIdentifier | ||
// Trigger represents the event that triggered the upkeep to be checked | ||
Trigger Trigger | ||
// WorkID represents the unit of work for the coordinated proposal | ||
WorkID string | ||
} | ||
|
||
// ReportedUpkeep contains details of an upkeep for which a report was generated. | ||
type ReportedUpkeep struct { | ||
// UpkeepID id of the underlying upkeep | ||
UpkeepID UpkeepIdentifier | ||
// Trigger data for the upkeep | ||
Trigger Trigger | ||
// WorkID represents the unit of work for the reported upkeep | ||
WorkID string | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ReplaceAll
is available:Although this seems odd. Is the point to keep it in human readable form in source?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh and there is a
Replacer
:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah so the init implementation is just to collapse the more readable source string into a single line; my understanding here is that using a JSON encoder was deemed to be too expensive at the time, and using a string template was cheaper/faster. I didn't want to modify any of the code lifted out of automation in this PR; I wanted to keep everything as it was written in the automation repo to keep the impacts of these changes to a minimum