Skip to content
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

VC: Add block scoring #6303

Merged
merged 2 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions AllTests-mainnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -905,12 +905,14 @@ OK: 1/1 Fail: 0/1 Skip: 0/1
+ getAggregatedAttestationDataScore() test vectors OK
+ getAttestationDataScore() test vectors OK
+ getLiveness() response deserialization test OK
+ getProduceBlockResponseV3Score() default test OK
+ getProduceBlockResponseV3Score() test vectors OK
+ getSyncCommitteeContributionDataScore() test vectors OK
+ getSyncCommitteeMessageDataScore() test vectors OK
+ getUniqueVotes() test vectors OK
+ normalizeUri() test vectors OK
```
OK: 12/12 Fail: 0/12 Skip: 0/12
OK: 14/14 Fail: 0/14 Skip: 0/14
## Validator change pool testing suite
```diff
+ addValidatorChangeMessage/getAttesterSlashingMessage OK
Expand Down Expand Up @@ -1030,4 +1032,4 @@ OK: 2/2 Fail: 0/2 Skip: 0/2
OK: 9/9 Fail: 0/9 Skip: 0/9

---TOTAL---
OK: 687/692 Fail: 0/692 Skip: 5/692
OK: 689/694 Fail: 0/694 Skip: 5/694
10 changes: 10 additions & 0 deletions beacon_chain/spec/forks.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1173,6 +1173,16 @@ template withForkyMaybeBlindedBlck*(
template forkyMaybeBlindedBlck: untyped {.inject, used.} = b.phase0Data
body

template shortLog*(x: ForkedMaybeBlindedBeaconBlock): auto =
withForkyMaybeBlindedBlck(x):
when consensusFork >= ConsensusFork.Deneb:
when isBlinded == true:
shortLog(forkyMaybeBlindedBlck)
else:
shortLog(forkyMaybeBlindedBlck.`block`)
else:
shortLog(forkyMaybeBlindedBlck)

template withStateAndBlck*(
s: ForkedHashedBeaconState,
b: ForkedBeaconBlock | ForkedSignedBeaconBlock |
Expand Down
98 changes: 80 additions & 18 deletions beacon_chain/validator_client/api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ type
status*: ApiOperation
data*: seq[ApiNodeResponse[T]]

ApiScore* = object
ApiScore*[T] = object
index*: int
score*: Opt[float64]
score*: Opt[T]

BestNodeResponse*[T] = object
BestNodeResponse*[T, X] = object
node*: BeaconNodeServerRef
data*: ApiResponse[T]
score*: float64
score*: X

const
ViableNodeStatus* = {
Expand All @@ -56,7 +56,7 @@ const
RestBeaconNodeStatus.Synced
}

proc `$`*(s: ApiScore): string =
proc `$`*[T](s: ApiScore[T]): string =
var res = Base10.toString(uint64(s.index))
res.add(": ")
if s.score.isSome():
Expand All @@ -65,22 +65,27 @@ proc `$`*(s: ApiScore): string =
res.add("<n/a>")
res

proc `$`*(ss: openArray[ApiScore]): string =
proc `$`*[T](ss: openArray[ApiScore[T]]): string =
"[" & ss.mapIt($it).join(",") & "]"

chronicles.formatIt(seq[ApiScore]):
$it

func init*(t: typedesc[ApiScore], node: BeaconNodeServerRef,
score: float64): ApiScore =
ApiScore(index: node.index, score: Opt.some(score))
score: float64): ApiScore[float64] =
ApiScore[float64](index: node.index, score: Opt.some(score))

func init*(t: typedesc[ApiScore], node: BeaconNodeServerRef): ApiScore =
ApiScore(index: node.index, score: Opt.none(float64))
func init*(t: typedesc[ApiScore], node: BeaconNodeServerRef,
score: UInt256): ApiScore[UInt256] =
ApiScore[UInt256](index: node.index, score: Opt.some(score))

func init*(tt: typedesc[ApiScore],
node: BeaconNodeServerRef, T: typedesc): ApiScore[T] =
ApiScore[T](index: node.index, score: Opt.none(T))

func init*[T](t: typedesc[BestNodeResponse], node: BeaconNodeServerRef,
data: ApiResponse[T], score: float64): BestNodeResponse[T] =
BestNodeResponse[T](node: node, data: data, score: score)
func init*[T, X](t: typedesc[BestNodeResponse], node: BeaconNodeServerRef,
data: ApiResponse[T], score: X): BestNodeResponse[T, X] =
BestNodeResponse[T, X](node: node, data: data, score: score)

proc lazyWaiter(node: BeaconNodeServerRef, request: FutureBase,
requestName: string, strategy: ApiStrategyKind) {.async.} =
Expand Down Expand Up @@ -234,7 +239,7 @@ template firstSuccessParallel*(
pendingNodes.del(index)

let
node {.inject.} = beaconNode
node {.inject, used.} = beaconNode
apiResponse {.inject.} =
apiResponseOr[responseType](requestFut, timerFut,
"Timeout exceeded while awaiting for the response")
Expand Down Expand Up @@ -283,6 +288,7 @@ template bestSuccess*(
vc: ValidatorClientRef,
responseType: typedesc,
handlerType: typedesc,
scoreType: typedesc,
timeout: Duration,
statuses: set[RestBeaconNodeStatus],
roles: set[BeaconNodeRole],
Expand All @@ -301,8 +307,8 @@ template bestSuccess*(

var
retRes: ApiResponse[handlerType]
scores: seq[ApiScore]
bestResponse: Opt[BestNodeResponse[handlerType]]
scores: seq[ApiScore[scoreType]]
bestResponse: Opt[BestNodeResponse[handlerType, scoreType]]

block mainLoop:
while true:
Expand Down Expand Up @@ -395,7 +401,7 @@ template bestSuccess*(
perfectScoreFound = true
break
else:
scores.add(ApiScore.init(node))
scores.add(ApiScore.init(node, scoreType))

if perfectScoreFound:
# lazyWait will cancel `pendingRequests` on timeout.
Expand Down Expand Up @@ -1181,6 +1187,7 @@ proc getHeadBlockRoot*(
let res = vc.bestSuccess(
RestPlainResponse,
GetBlockRootResponse,
float64,
SlotDuration,
ViableNodeStatus,
{BeaconNodeRole.SyncCommitteeData},
Expand Down Expand Up @@ -1413,6 +1420,7 @@ proc produceAttestationData*(
let res = vc.bestSuccess(
RestPlainResponse,
ProduceAttestationDataResponse,
float64,
OneThirdDuration,
ViableNodeStatus,
{BeaconNodeRole.AttestationData},
Expand Down Expand Up @@ -1685,6 +1693,7 @@ proc getAggregatedAttestation*(
let res = vc.bestSuccess(
RestPlainResponse,
GetAggregatedAttestationResponse,
float64,
OneThirdDuration,
ViableNodeStatus,
{BeaconNodeRole.AggregatedData},
Expand Down Expand Up @@ -1818,6 +1827,7 @@ proc produceSyncCommitteeContribution*(
let res = vc.bestSuccess(
RestPlainResponse,
ProduceSyncCommitteeContributionResponse,
float64,
OneThirdDuration,
ViableNodeStatus,
{BeaconNodeRole.SyncCommitteeData},
Expand Down Expand Up @@ -2036,7 +2046,59 @@ proc produceBlockV3*(
var failures: seq[ApiNodeFailure]

case strategy
of ApiStrategyKind.First, ApiStrategyKind.Best:
of ApiStrategyKind.Best:
let res = vc.bestSuccess(
RestPlainResponse,
ProduceBlockResponseV3,
UInt256,
SlotDuration,
ViableNodeStatus,
{BeaconNodeRole.BlockProposalData},
produceBlockV3Plain(it, slot, randao_reveal, graffiti,
builder_boost_factor),
getProduceBlockResponseV3Score(itresponse)):
if apiResponse.isErr():
handleCommunicationError()
ApiResponse[ProduceBlockResponseV3].err(apiResponse.error)
else:
let response = apiResponse.get()
case response.status
of 200:
let
version = response.headers.getString("eth-consensus-version")
blinded =
response.headers.getString("eth-execution-payload-blinded")
executionValue =
response.headers.getString("eth-execution-payload-value")
consensusValue =
response.headers.getString("eth-consensus-block-value")
res = decodeBytes(ProduceBlockResponseV3, response.data,
response.contentType, version, blinded,
executionValue, consensusValue)
if res.isErr():
handleUnexpectedData()
ApiResponse[ProduceBlockResponseV3].err($res.error)
else:
ApiResponse[ProduceBlockResponseV3].ok(res.get())
of 400:
handle400()
ApiResponse[ProduceBlockResponseV3].err(ResponseInvalidError)
of 500:
handle500()
ApiResponse[ProduceBlockResponseV3].err(ResponseInternalError)
of 503:
handle503()
ApiResponse[ProduceBlockResponseV3].err(
ResponseNoSyncError)
else:
handleUnexpectedCode()
ApiResponse[ProduceBlockResponseV3].err(
ResponseUnexpectedError)
if res.isErr():
raise (ref ValidatorApiError)(msg: res.error, data: failures)
return res.get()

of ApiStrategyKind.First:
let res = vc.firstSuccessParallel(
RestPlainResponse,
ProduceBlockResponseV3,
Expand Down
32 changes: 32 additions & 0 deletions beacon_chain/validator_client/scoring.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import std/strutils
import ssz_serialization/[types, bitseqs]
import stew/endians2
import stint
import nimcrypto/hash
import "."/common

Expand All @@ -24,6 +25,9 @@ const
func perfectScore*(score: float64): bool =
score == Inf

func perfectScore*(score: UInt256): bool =
score == high(UInt256)

proc shortScore*(score: float64): string =
if score == Inf:
"<perfect>"
Expand All @@ -32,6 +36,9 @@ proc shortScore*(score: float64): string =
else:
formatFloat(score, ffDecimal, 4)

proc shortScore*(score: UInt256): string =
$score

func getLexicographicScore(digest: Eth2Digest): float64 =
# We calculate score on first 8 bytes of digest.
let
Expand Down Expand Up @@ -183,3 +190,28 @@ proc getUniqueVotes*(attestations: openArray[phase0.Attestation]): int =
processVotes(attestation)
res += count
res

proc getProduceBlockResponseV3Score*(blck: ProduceBlockResponseV3): UInt256 =
let (res, cv, ev) =
block:
var score256 = UInt256.zero
let
cvalue =
if blck.consensusValue.isSome():
let value = blck.consensusValue.get()
score256 = score256 + value
$value
else:
"<missing>"
evalue =
if blck.executionValue.isSome():
let value = blck.executionValue.get()
score256 = score256 + value
$value
else:
"<missing>"
(score256, cvalue, evalue)

debug "Block score", blck = shortLog(blck), consensus_value = cv,
execution_value = ev, score = shortScore(res)
res
47 changes: 45 additions & 2 deletions tests/test_validator_client.nim
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ const
("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01", "0.9995"),
("0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000101", "0.0005"),
]

ContributionDataVectors = [
("0xffffffffffffffffffffffffffff7f7f", "0.9844"),
("0xffffffffffffffffffffffff7f7f7f7f", "0.9688"),
Expand Down Expand Up @@ -446,6 +447,15 @@ const
([("0xff01", Slot(0), 0'u64), ("0xff01", Slot(0), 1'u64)], 16)
]

UInt256ScoreVectors = [
("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"0x0000000000000000000000000000000000000000000000000000000000000001",
"0"),
("0x10",
"0x10",
"32")
]

proc init(t: typedesc[Eth2Digest], data: string): Eth2Digest =
let length = len(data)
var dst = Eth2Digest()
Expand Down Expand Up @@ -755,6 +765,25 @@ suite "Validator Client test suite":
score == "0.0000"
isLowestScoreAggregatedAttestation(adata.data) == true

test "getProduceBlockResponseV3Score() default test":
let
bdata1 = ProduceBlockResponseV3()
bdata2 = ProduceBlockResponseV3(
consensusValue: Opt.some(UInt256.zero)
)
bdata3 = ProduceBlockResponseV3(
executionValue: Opt.some(UInt256.zero),
)
bdata4 = ProduceBlockResponseV3(
consensusValue: Opt.some(UInt256.zero),
executionValue: Opt.some(UInt256.zero)
)
check:
shortScore(getProduceBlockResponseV3Score(bdata1)) == "0"
shortScore(getProduceBlockResponseV3Score(bdata2)) == "0"
shortScore(getProduceBlockResponseV3Score(bdata3)) == "0"
shortScore(getProduceBlockResponseV3Score(bdata4)) == "0"

test "getSyncCommitteeContributionDataScore() test vectors":
for vector in ContributionDataVectors:
let
Expand All @@ -773,11 +802,24 @@ suite "Validator Client test suite":
check:
score == vector[5]

test "getProduceBlockResponseV3Score() test vectors":
for vector in UInt256ScoreVectors:
let
value1 = strictParse(vector[0], UInt256, 16).get()
value2 = strictParse(vector[1], UInt256, 16).get()
bdata = ProduceBlockResponseV3(
executionValue: Opt.some(value1),
consensusValue: Opt.some(value2)
)
check shortScore(getProduceBlockResponseV3Score(bdata)) == vector[2]

test "getUniqueVotes() test vectors":
for vector in AttestationBitsVectors:
let
a1 = phase0.Attestation.init(vector[0][0][0], vector[0][0][1], vector[0][0][2])
a2 = phase0.Attestation.init(vector[0][1][0], vector[0][1][1], vector[0][1][2])
a1 = phase0.Attestation.init(vector[0][0][0], vector[0][0][1],
vector[0][0][2])
a2 = phase0.Attestation.init(vector[0][1][0], vector[0][1][1],
vector[0][1][2])
check getUniqueVotes([a1, a2]) == vector[1]

asyncTest "firstSuccessParallel() API timeout test":
Expand Down Expand Up @@ -850,6 +892,7 @@ suite "Validator Client test suite":
let response = vc.bestSuccess(
RestPlainResponse,
uint64,
float64,
100.milliseconds,
AllBeaconNodeStatuses,
{BeaconNodeRole.Duties},
Expand Down
Loading