VC: Add block scoring (#6303)

* Add scoring for blocks.

* Update Alltests.
This commit is contained in:
Eugene Kabanov 2024-05-29 13:07:39 +03:00 committed by GitHub
parent dc6951eee9
commit 1cdb32222b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 171 additions and 22 deletions

View File

@ -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
@ -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

View File

@ -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 |

View File

@ -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* = {
@ -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():
@ -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*[T](t: typedesc[BestNodeResponse], node: BeaconNodeServerRef,
data: ApiResponse[T], score: float64): BestNodeResponse[T] =
BestNodeResponse[T](node: node, data: data, score: score)
func init*(tt: typedesc[ApiScore],
node: BeaconNodeServerRef, T: typedesc): ApiScore[T] =
ApiScore[T](index: node.index, score: Opt.none(T))
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.} =
@ -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")
@ -283,6 +288,7 @@ template bestSuccess*(
vc: ValidatorClientRef,
responseType: typedesc,
handlerType: typedesc,
scoreType: typedesc,
timeout: Duration,
statuses: set[RestBeaconNodeStatus],
roles: set[BeaconNodeRole],
@ -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:
@ -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.
@ -1181,6 +1187,7 @@ proc getHeadBlockRoot*(
let res = vc.bestSuccess(
RestPlainResponse,
GetBlockRootResponse,
float64,
SlotDuration,
ViableNodeStatus,
{BeaconNodeRole.SyncCommitteeData},
@ -1413,6 +1420,7 @@ proc produceAttestationData*(
let res = vc.bestSuccess(
RestPlainResponse,
ProduceAttestationDataResponse,
float64,
OneThirdDuration,
ViableNodeStatus,
{BeaconNodeRole.AttestationData},
@ -1685,6 +1693,7 @@ proc getAggregatedAttestation*(
let res = vc.bestSuccess(
RestPlainResponse,
GetAggregatedAttestationResponse,
float64,
OneThirdDuration,
ViableNodeStatus,
{BeaconNodeRole.AggregatedData},
@ -1818,6 +1827,7 @@ proc produceSyncCommitteeContribution*(
let res = vc.bestSuccess(
RestPlainResponse,
ProduceSyncCommitteeContributionResponse,
float64,
OneThirdDuration,
ViableNodeStatus,
{BeaconNodeRole.SyncCommitteeData},
@ -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,

View File

@ -10,6 +10,7 @@
import std/strutils
import ssz_serialization/[types, bitseqs]
import stew/endians2
import stint
import nimcrypto/hash
import "."/common
@ -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>"
@ -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
@ -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

View File

@ -389,6 +389,7 @@ const
("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01", "0.9995"),
("0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000101", "0.0005"),
]
ContributionDataVectors = [
("0xffffffffffffffffffffffffffff7f7f", "0.9844"),
("0xffffffffffffffffffffffff7f7f7f7f", "0.9688"),
@ -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()
@ -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
@ -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":
@ -850,6 +892,7 @@ suite "Validator Client test suite":
let response = vc.bestSuccess(
RestPlainResponse,
uint64,
float64,
100.milliseconds,
AllBeaconNodeStatuses,
{BeaconNodeRole.Duties},