2022-05-18 16:12:54 +00:00
// VulcanizeDB
// Copyright © 2022 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2022-05-09 18:44:27 +00:00
package beaconclient_test
import (
2022-05-12 13:52:13 +00:00
"context"
"encoding/hex"
"encoding/json"
2022-05-09 18:44:27 +00:00
"fmt"
"net/http"
"os"
"path/filepath"
2022-05-12 13:52:13 +00:00
"strconv"
"sync/atomic"
2022-05-09 18:44:27 +00:00
"time"
2022-05-12 13:52:13 +00:00
"github.com/jarcoal/httpmock"
2022-05-09 18:44:27 +00:00
. "github.com/onsi/ginkgo/v2"
2022-05-17 20:05:15 +00:00
"github.com/prysmaticlabs/prysm/beacon-chain/state"
si "github.com/prysmaticlabs/prysm/consensus-types/interfaces"
2022-05-12 13:52:13 +00:00
types "github.com/prysmaticlabs/prysm/consensus-types/primitives"
2022-05-17 20:05:15 +00:00
dt "github.com/prysmaticlabs/prysm/encoding/ssz/detect"
2022-05-12 13:52:13 +00:00
st "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
"github.com/r3labs/sse"
log "github.com/sirupsen/logrus"
. "github.com/onsi/gomega"
2022-05-09 18:44:27 +00:00
"github.com/vulcanize/ipld-ethcl-indexer/pkg/beaconclient"
2022-05-12 13:52:13 +00:00
"github.com/vulcanize/ipld-ethcl-indexer/pkg/database/sql"
"github.com/vulcanize/ipld-ethcl-indexer/pkg/database/sql/postgres"
2022-05-09 18:44:27 +00:00
)
2022-05-24 20:18:55 +00:00
var (
address string = "localhost"
port int = 8080
protocol string = "http"
dbHost string = "localhost"
dbPort int = 8076
dbName string = "vulcanize_testing"
dbUser string = "vdbm"
dbPassword string = "password"
dbDriver string = "pgx"
2022-06-03 16:47:13 +00:00
bcUniqueIdentifier int = 100
2022-05-24 20:18:55 +00:00
dummyParentRoot string = "46f98c08b54a71dfda4d56e29ec3952b8300cd8d6b67a9b6c562ae96a7a25a42"
knownGapsTableIncrement int = 100000
2022-06-08 15:38:42 +00:00
maxRetry int = 160
2022-05-24 20:18:55 +00:00
TestEvents = map [ string ] Message {
2022-06-08 14:26:27 +00:00
"0" : {
HeadMessage : beaconclient . Head {
Slot : "0" ,
Block : "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" ,
State : "0x7e76880eb67bbdc86250aa578958e9d0675e64e714337855204fb5abaaf82c2b" ,
CurrentDutyDependentRoot : "" ,
PreviousDutyDependentRoot : "" ,
EpochTransition : false ,
ExecutionOptimistic : false ,
} ,
2022-06-08 15:14:01 +00:00
SignedBeaconBlock : filepath . Join ( "ssz-data" , "0" , "signed-beacon-block.ssz" ) ,
BeaconState : filepath . Join ( "ssz-data" , "0" , "beacon-state.ssz" ) ,
CorrectSignedBeaconBlockMhKey : "/blocks/QLVAEQRQPA2GINRRGFSDKYRZGNTGIYLCGY4TAMJTME3WMMDBGJTDSNRRMNQWGYJQMM4DKM3GHA3WGZTFHE2TSNLGMU2TAMBTHAYTMMZQG44TGNRQ" ,
CorrectBeaconStateMhKey : "/blocks/QLVAEQRQPA3WKNZWHA4DAZLCGY3WEYTEMM4DMMRVGBQWCNJXHA4TKODFHFSDANRXGVSTMNDFG4YTIMZTG44DKNJSGA2GMYRVMFRGCYLGHAZGGMTC" ,
CorrectParentRoot : "0x0000000000000000000000000000000000000000000000000000000000000000" ,
CorrectEth1BlockHash : "0x0000000000000000000000000000000000000000000000000000000000000000" ,
2022-06-08 14:26:27 +00:00
} ,
2022-05-24 20:18:55 +00:00
"100-dummy" : {
HeadMessage : beaconclient . Head {
Slot : "100" ,
Block : "04955400371347e26f61d7a4bbda5b23fa0b25d5fc465160f2a92d52a63b919b" ,
State : "36d5c9a129979b4502bd9a06e57a742810ecbc3fa55a0361c0723c92c1782bfa" ,
CurrentDutyDependentRoot : "" ,
PreviousDutyDependentRoot : "" ,
EpochTransition : false ,
ExecutionOptimistic : false ,
} ,
TestNotes : "A block that is supposed to replicate slot 100, but contains some dummy test information." ,
MimicConfig : & MimicConfig {
ForkVersion : "phase0" ,
} ,
SignedBeaconBlock : filepath . Join ( "ssz-data" , "100" , "signed-beacon-block.ssz" ) ,
BeaconState : filepath . Join ( "ssz-data" , "100" , "beacon-state.ssz" ) ,
} ,
"100-dummy-2" : {
HeadMessage : beaconclient . Head {
Slot : "100" ,
Block : "04955400371347e26f61d7a4bbda5b23fa0b25d5fc465160f2a9aaaaaaaaaaaa" ,
State : "36d5c9a129979b4502bd9a06e57a742810ecbc3fa55a0361c072bbbbbbbbbbbb" ,
CurrentDutyDependentRoot : "" ,
PreviousDutyDependentRoot : "" ,
EpochTransition : false ,
ExecutionOptimistic : false ,
} ,
TestNotes : "A block that is supposed to replicate slot 100, but contains some dummy test information." ,
MimicConfig : & MimicConfig {
ForkVersion : "phase0" ,
} ,
SignedBeaconBlock : filepath . Join ( "ssz-data" , "100" , "signed-beacon-block.ssz" ) ,
BeaconState : filepath . Join ( "ssz-data" , "100" , "beacon-state.ssz" ) ,
} ,
"102-wrong-ssz-1" : {
HeadMessage : beaconclient . Head {
Slot : "102" ,
Block : "0x46f98c08b54a71dfda4d56e29ec3952b8300cd8d6b67a9b6c562ae96a7a25a42" ,
State : "0x9b20b114c613c1aa462e02d590b3da902b0a1377e938ed0f94dd3491d763ef67" ,
CurrentDutyDependentRoot : "" ,
PreviousDutyDependentRoot : "" ,
EpochTransition : false ,
ExecutionOptimistic : false ,
} ,
TestNotes : "A bad block that returns the wrong ssz objects, used for testing incorrect SSZ decoding." ,
BeaconState : filepath . Join ( "ssz-data" , "102" , "signed-beacon-block.ssz" ) ,
SignedBeaconBlock : filepath . Join ( "ssz-data" , "102" , "beacon-state.ssz" ) ,
} ,
"100" : {
HeadMessage : beaconclient . Head {
Slot : "100" ,
Block : "0x582187e97f7520bb69eea014c3834c964c45259372a0eaaea3f032013797996b" ,
State : "0xf286a0379c0386a3c7be28d05d829f8eb7b280cc9ede15449af20ebcd06a7a56" ,
CurrentDutyDependentRoot : "" ,
PreviousDutyDependentRoot : "" ,
EpochTransition : false ,
ExecutionOptimistic : false ,
} ,
2022-06-08 15:14:01 +00:00
TestNotes : "An easy to process Phase 0 block" ,
SignedBeaconBlock : filepath . Join ( "ssz-data" , "100" , "signed-beacon-block.ssz" ) ,
BeaconState : filepath . Join ( "ssz-data" , "100" , "beacon-state.ssz" ) ,
CorrectSignedBeaconBlockMhKey : "/blocks/QLVAEQRQPA2TQMRRHA3WKOJXMY3TKMRQMJRDMOLFMVQTAMJUMMZTQMZUMM4TMNDDGQ2TENJZGM3TEYJQMVQWCZLBGNTDAMZSGAYTGNZZG44TSNTC" ,
CorrectBeaconStateMhKey : "/blocks/QLVAEQRQPBTDEOBWMEYDGNZZMMYDGOBWMEZWGN3CMUZDQZBQGVSDQMRZMY4GKYRXMIZDQMDDMM4WKZDFGE2TINBZMFTDEMDFMJRWIMBWME3WCNJW" ,
CorrectParentRoot : "0x629ae1587895043076500f4f5dcb202a47c2fc95d5b5c548cb83bc97bd2dbfe1" ,
CorrectEth1BlockHash : "0x8d3f027beef5cbd4f8b29fc831aba67a5d74768edca529f5596f07fd207865e1" ,
2022-05-24 20:18:55 +00:00
} ,
"101" : {
HeadMessage : beaconclient . Head {
Slot : "101" ,
Block : "0xabe1a972e512182d04f0d4a5c9c25f9ee57c2e9d0ff3f4c4c82fd42d13d31083" ,
State : "0xcb04aa2edbf13c7bb7e7bd9b621ced6832e0075e89147352eac3019a824ce847" ,
CurrentDutyDependentRoot : "" ,
PreviousDutyDependentRoot : "" ,
EpochTransition : false ,
ExecutionOptimistic : false ,
} ,
2022-06-08 15:14:01 +00:00
TestNotes : "An easy to process Phase 0 block" ,
SignedBeaconBlock : filepath . Join ( "ssz-data" , "101" , "signed-beacon-block.ssz" ) ,
BeaconState : filepath . Join ( "ssz-data" , "101" , "beacon-state.ssz" ) ,
CorrectEth1BlockHash : "0x8d3f027beef5cbd4f8b29fc831aba67a5d74768edca529f5596f07fd207865e1" ,
CorrectSignedBeaconBlockMhKey : "/blocks/QLVAEQRQPBQWEZJRME4TOMTFGUYTEMJYGJSDANDGGBSDIYJVMM4WGMRVMY4WKZJVG5RTEZJZMQYGMZRTMY2GGNDDHAZGMZBUGJSDCM3EGMYTAOBT" ,
CorrectBeaconStateMhKey : "/blocks/QLVAEQRQPBRWEMBUMFQTEZLEMJTDCM3DG5RGEN3FG5RGIOLCGYZDCY3FMQ3DQMZSMUYDANZVMU4DSMJUG4ZTKMTFMFRTGMBRHFQTQMRUMNSTQNBX" ,
2022-05-24 20:18:55 +00:00
} ,
"2375703-dummy" : {
HeadMessage : beaconclient . Head {
Slot : "2375703" ,
Block : "c9fb337b62e2a0dae4f27ab49913132570f7f2cab3f23ad99f4d07508a8e648e" ,
State : "0299a145bcda2c8f5e7d2e068ee101861edbee2ec1db2d5e1d850b0d265aef5f" ,
CurrentDutyDependentRoot : "" ,
PreviousDutyDependentRoot : "" ,
EpochTransition : false ,
ExecutionOptimistic : false ,
} ,
TestNotes : "This is a dummy message that is used for reorgs" ,
MimicConfig : & MimicConfig {
ForkVersion : "altair" ,
} ,
SignedBeaconBlock : filepath . Join ( "ssz-data" , "2375703" , "signed-beacon-block.ssz" ) ,
BeaconState : filepath . Join ( "ssz-data" , "2375703" , "beacon-state.ssz" ) ,
} ,
"2375703-dummy-2" : {
HeadMessage : beaconclient . Head {
Slot : "2375703" ,
Block : "c9fb337b62e2a0dae4f27ab49913132570f7f2cab3f23ad99f4d07508aaaaaaa" ,
State : "0299a145bcda2c8f5e7d2e068ee101861edbee2ec1db2d5e1d850b0d2bbbbbbb" ,
CurrentDutyDependentRoot : "" ,
PreviousDutyDependentRoot : "" ,
EpochTransition : false ,
ExecutionOptimistic : false ,
} ,
TestNotes : "This is a dummy message that is used for reorgs" ,
MimicConfig : & MimicConfig {
ForkVersion : "altair" ,
} ,
SignedBeaconBlock : filepath . Join ( "ssz-data" , "2375703" , "signed-beacon-block.ssz" ) ,
BeaconState : filepath . Join ( "ssz-data" , "2375703" , "beacon-state.ssz" ) ,
} ,
"2375703" : {
HeadMessage : beaconclient . Head {
Slot : "2375703" ,
Block : "0x4392372c5f6e39499e31bf924388b5815639103149f0f54f8a453773b1802301" ,
State : "0xb6215b560273af63ec7e011572b60ec1ca0b0232f8ff44fcd4ed55c7526e964e" ,
CurrentDutyDependentRoot : "" , PreviousDutyDependentRoot : "" , EpochTransition : false , ExecutionOptimistic : false } ,
2022-06-08 15:14:01 +00:00
TestNotes : "An easy to process Altair Block" ,
SignedBeaconBlock : filepath . Join ( "ssz-data" , "2375703" , "signed-beacon-block.ssz" ) ,
BeaconState : filepath . Join ( "ssz-data" , "2375703" , "beacon-state.ssz" ) ,
CorrectEth1BlockHash : "0xd74b1c60423651624de6bb301ac25808951c167ba6ecdd9b2e79b4315aee8202" ,
CorrectParentRoot : "0x08736ddc20b77f65d1aa6301f7e6e856a820ff3ce6430ed2c3694ae35580e740" ,
CorrectSignedBeaconBlockMhKey : "/blocks/QLVAEQRQPA2DGOJSGM3TEYZVMY3GKMZZGQ4TSZJTGFRGMOJSGQZTQODCGU4DCNJWGM4TCMBTGE2DSZRQMY2TIZRYME2DKMZXG4ZWEMJYGAZDGMBR" ,
CorrectBeaconStateMhKey : "/blocks/QLVAEQRQPBRDMMRRGVRDKNRQGI3TGYLGGYZWKYZXMUYDCMJVG4ZGENRQMVRTCY3BGBRDAMRTGJTDQZTGGQ2GMY3EGRSWINJVMM3TKMRWMU4TMNDF" ,
2022-05-24 20:18:55 +00:00
} ,
"3797056" : {
HeadMessage : beaconclient . Head {
Slot : "3797056" ,
Block : "" ,
2022-06-08 14:26:27 +00:00
State : "" ,
2022-05-24 20:18:55 +00:00
CurrentDutyDependentRoot : "" , PreviousDutyDependentRoot : "" , EpochTransition : false , ExecutionOptimistic : false } ,
2022-06-08 14:26:27 +00:00
TestNotes : "An easy to process Altair Block" ,
// The file below should not exist, this will trigger an error message and 404 response from the mock.
SignedBeaconBlock : filepath . Join ( "ssz-data" , "3797056" , "should-not-exist.txt" ) ,
BeaconState : filepath . Join ( "ssz-data" , "3797056" , "beacon-state.ssz" ) ,
2022-05-24 20:18:55 +00:00
} ,
}
TestConfig = Config {
protocol : protocol ,
address : address ,
port : port ,
dummyParentRoot : dummyParentRoot ,
dbHost : dbHost ,
dbPort : dbPort ,
dbName : dbName ,
dbUser : dbUser ,
dbPassword : dbPassword ,
dbDriver : dbDriver ,
knownGapsTableIncrement : knownGapsTableIncrement ,
2022-06-03 16:47:13 +00:00
bcUniqueIdentifier : bcUniqueIdentifier ,
2022-06-06 13:02:43 +00:00
checkDb : true ,
2022-05-24 20:18:55 +00:00
}
BeaconNodeTester = TestBeaconNode {
TestEvents : TestEvents ,
TestConfig : TestConfig ,
}
)
2022-05-09 18:44:27 +00:00
type Message struct {
2022-06-08 15:14:01 +00:00
HeadMessage beaconclient . Head // The head messsage that will be streamed to the BeaconClient
TestNotes string // A small explanation of the purpose this structure plays in the testing landscape.
MimicConfig * MimicConfig // A configuration of parameters that you are trying to
SignedBeaconBlock string // The file path output of an SSZ encoded SignedBeaconBlock.
BeaconState string // The file path output of an SSZ encoded BeaconState.
CorrectSignedBeaconBlockMhKey string // The correct MhKey for the signedBeaconBlock
CorrectBeaconStateMhKey string // The correct MhKey beaconState
CorrectParentRoot string // The correct parent root
CorrectEth1BlockHash string // The correct eth1blockHash
2022-05-09 18:44:27 +00:00
}
2022-05-12 13:52:13 +00:00
// A structure that can be utilized to mimic and existing SSZ object but change it ever so slightly.
// This is used because creating your own SSZ object is a headache.
type MimicConfig struct {
2022-05-17 20:05:15 +00:00
ParentRoot string // The parent root, leave it empty if you want a to use the universal
ForkVersion string // Specify the fork version. This is needed as a workaround to create dummy SignedBeaconBlocks.
2022-05-12 13:52:13 +00:00
}
2022-05-09 18:44:27 +00:00
2022-06-08 14:26:27 +00:00
var _ = Describe ( "Capturehead" , Label ( "head" ) , func ( ) {
2022-05-12 13:52:13 +00:00
Describe ( "Receiving New Head SSE messages" , Label ( "unit" , "behavioral" ) , func ( ) {
Context ( "Correctly formatted Phase0 Block" , func ( ) {
It ( "Should turn it into a struct successfully." , func ( ) {
2022-05-17 20:05:15 +00:00
bc := setUpTest ( BeaconNodeTester . TestConfig , "99" )
BeaconNodeTester . SetupBeaconNodeMock ( BeaconNodeTester . TestEvents , BeaconNodeTester . TestConfig . protocol , BeaconNodeTester . TestConfig . address , BeaconNodeTester . TestConfig . port , BeaconNodeTester . TestConfig . dummyParentRoot )
defer httpmock . DeactivateAndReset ( )
BeaconNodeTester . testProcessBlock ( bc , BeaconNodeTester . TestEvents [ "100" ] . HeadMessage , 3 , maxRetry , 1 , 0 , 0 )
2022-06-08 15:20:17 +00:00
validateSignedBeaconBlock ( bc , BeaconNodeTester . TestEvents [ "100" ] . HeadMessage , BeaconNodeTester . TestEvents [ "100" ] . CorrectParentRoot , BeaconNodeTester . TestEvents [ "100" ] . CorrectEth1BlockHash , BeaconNodeTester . TestEvents [ "100" ] . CorrectSignedBeaconBlockMhKey )
2022-06-08 15:14:01 +00:00
validateBeaconState ( bc , BeaconNodeTester . TestEvents [ "100" ] . HeadMessage , BeaconNodeTester . TestEvents [ "100" ] . CorrectBeaconStateMhKey )
2022-05-12 13:52:13 +00:00
} )
} )
Context ( "Correctly formatted Altair Block" , func ( ) {
2022-05-09 18:44:27 +00:00
It ( "Should turn it into a struct successfully." , func ( ) {
2022-05-17 20:05:15 +00:00
bc := setUpTest ( BeaconNodeTester . TestConfig , "2375702" )
BeaconNodeTester . SetupBeaconNodeMock ( BeaconNodeTester . TestEvents , BeaconNodeTester . TestConfig . protocol , BeaconNodeTester . TestConfig . address , BeaconNodeTester . TestConfig . port , BeaconNodeTester . TestConfig . dummyParentRoot )
defer httpmock . DeactivateAndReset ( )
BeaconNodeTester . testProcessBlock ( bc , BeaconNodeTester . TestEvents [ "2375703" ] . HeadMessage , 74240 , maxRetry , 1 , 0 , 0 )
2022-06-08 15:20:17 +00:00
validateSignedBeaconBlock ( bc , BeaconNodeTester . TestEvents [ "2375703" ] . HeadMessage , BeaconNodeTester . TestEvents [ "2375703" ] . CorrectParentRoot , BeaconNodeTester . TestEvents [ "2375703" ] . CorrectEth1BlockHash , BeaconNodeTester . TestEvents [ "2375703" ] . CorrectSignedBeaconBlockMhKey )
2022-06-08 15:14:01 +00:00
validateBeaconState ( bc , BeaconNodeTester . TestEvents [ "2375703" ] . HeadMessage , BeaconNodeTester . TestEvents [ "2375703" ] . CorrectBeaconStateMhKey )
2022-05-09 18:44:27 +00:00
} )
} )
2022-05-17 20:05:15 +00:00
Context ( "Correctly formatted Altair Test Blocks" , func ( ) {
It ( "Should turn it into a struct successfully." , func ( ) {
bc := setUpTest ( BeaconNodeTester . TestConfig , "2375702" )
BeaconNodeTester . SetupBeaconNodeMock ( BeaconNodeTester . TestEvents , BeaconNodeTester . TestConfig . protocol , BeaconNodeTester . TestConfig . address , BeaconNodeTester . TestConfig . port , BeaconNodeTester . TestConfig . dummyParentRoot )
defer httpmock . DeactivateAndReset ( )
BeaconNodeTester . testProcessBlock ( bc , BeaconNodeTester . TestEvents [ "2375703-dummy" ] . HeadMessage , 74240 , maxRetry , 1 , 0 , 0 )
bc = setUpTest ( BeaconNodeTester . TestConfig , "2375702" )
BeaconNodeTester . SetupBeaconNodeMock ( BeaconNodeTester . TestEvents , BeaconNodeTester . TestConfig . protocol , BeaconNodeTester . TestConfig . address , BeaconNodeTester . TestConfig . port , BeaconNodeTester . TestConfig . dummyParentRoot )
defer httpmock . DeactivateAndReset ( )
BeaconNodeTester . testProcessBlock ( bc , BeaconNodeTester . TestEvents [ "2375703-dummy-2" ] . HeadMessage , 74240 , maxRetry , 1 , 0 , 0 )
} )
} )
Context ( "Correctly formatted Phase0 Test Blocks" , func ( ) {
It ( "Should turn it into a struct successfully." , func ( ) {
bc := setUpTest ( BeaconNodeTester . TestConfig , "99" )
BeaconNodeTester . SetupBeaconNodeMock ( BeaconNodeTester . TestEvents , BeaconNodeTester . TestConfig . protocol , BeaconNodeTester . TestConfig . address , BeaconNodeTester . TestConfig . port , BeaconNodeTester . TestConfig . dummyParentRoot )
defer httpmock . DeactivateAndReset ( )
BeaconNodeTester . testProcessBlock ( bc , BeaconNodeTester . TestEvents [ "100-dummy" ] . HeadMessage , 3 , maxRetry , 1 , 0 , 0 )
bc = setUpTest ( BeaconNodeTester . TestConfig , "99" )
BeaconNodeTester . SetupBeaconNodeMock ( BeaconNodeTester . TestEvents , BeaconNodeTester . TestConfig . protocol , BeaconNodeTester . TestConfig . address , BeaconNodeTester . TestConfig . port , BeaconNodeTester . TestConfig . dummyParentRoot )
defer httpmock . DeactivateAndReset ( )
BeaconNodeTester . testProcessBlock ( bc , BeaconNodeTester . TestEvents [ "100-dummy-2" ] . HeadMessage , 3 , maxRetry , 1 , 0 , 0 )
} )
} )
Context ( "Two consecutive correct blocks" , func ( ) {
It ( "Should handle both blocks correctly, without any reorgs or known_gaps" , func ( ) {
bc := setUpTest ( BeaconNodeTester . TestConfig , "99" )
BeaconNodeTester . SetupBeaconNodeMock ( BeaconNodeTester . TestEvents , BeaconNodeTester . TestConfig . protocol , BeaconNodeTester . TestConfig . address , BeaconNodeTester . TestConfig . port , BeaconNodeTester . TestConfig . dummyParentRoot )
defer httpmock . DeactivateAndReset ( )
BeaconNodeTester . testProcessBlock ( bc , BeaconNodeTester . TestEvents [ "100" ] . HeadMessage , 3 , maxRetry , 1 , 0 , 0 )
BeaconNodeTester . testProcessBlock ( bc , BeaconNodeTester . TestEvents [ "101" ] . HeadMessage , 3 , maxRetry , 1 , 0 , 0 )
} )
} )
Context ( "Two consecutive blocks with a bad parent" , func ( ) {
It ( "Should add the previous block to the knownGaps table." , func ( ) {
bc := setUpTest ( BeaconNodeTester . TestConfig , "99" )
BeaconNodeTester . SetupBeaconNodeMock ( BeaconNodeTester . TestEvents , BeaconNodeTester . TestConfig . protocol , BeaconNodeTester . TestConfig . address , BeaconNodeTester . TestConfig . port , BeaconNodeTester . TestConfig . dummyParentRoot )
defer httpmock . DeactivateAndReset ( )
BeaconNodeTester . testProcessBlock ( bc , BeaconNodeTester . TestEvents [ "100-dummy" ] . HeadMessage , 3 , maxRetry , 1 , 0 , 0 )
BeaconNodeTester . testProcessBlock ( bc , BeaconNodeTester . TestEvents [ "101" ] . HeadMessage , 3 , maxRetry , 1 , 1 , 1 )
} )
} )
Context ( "Phase 0: We have a correctly formated SSZ SignedBeaconBlock and BeaconState" , func ( ) {
It ( "Should be able to get each objects root hash." , func ( ) {
testSszRoot ( BeaconNodeTester . TestEvents [ "100" ] )
} )
} )
Context ( "Altair: We have a correctly formated SSZ SignedBeaconBlock and BeaconState" , func ( ) {
It ( "Should be able to get each objects root hash." , func ( ) {
testSszRoot ( BeaconNodeTester . TestEvents [ "2375703" ] )
} )
} )
//Context("When there is a skipped slot", func() {
// It("Should indicate that the slot was skipped", func() {
2022-05-12 13:52:13 +00:00
// })
//})
2022-05-24 20:18:55 +00:00
Context ( "When the proper SSZ objects are not served" , func ( ) {
2022-05-17 20:05:15 +00:00
It ( "Should return an error, and add the slot to the knownGaps table." , func ( ) {
bc := setUpTest ( BeaconNodeTester . TestConfig , "101" )
BeaconNodeTester . SetupBeaconNodeMock ( BeaconNodeTester . TestEvents , BeaconNodeTester . TestConfig . protocol , BeaconNodeTester . TestConfig . address , BeaconNodeTester . TestConfig . port , BeaconNodeTester . TestConfig . dummyParentRoot )
defer httpmock . DeactivateAndReset ( )
BeaconNodeTester . testProcessBlock ( bc , BeaconNodeTester . TestEvents [ "102-wrong-ssz-1" ] . HeadMessage , 3 , maxRetry , 0 , 1 , 0 )
knownGapCount := countKnownGapsTable ( bc . Db )
Expect ( knownGapCount ) . To ( Equal ( 1 ) )
start , end := queryKnownGaps ( bc . Db , "102" , "102" )
Expect ( start ) . To ( Equal ( 102 ) )
Expect ( end ) . To ( Equal ( 102 ) )
} )
} )
} )
Describe ( "Known Gaps Scenario" , Label ( "unit" , "behavioral" ) , func ( ) {
Context ( "There is a gap at start up within one incrementing range." , func ( ) {
It ( "Should add only a single entry to the knownGaps table." , func ( ) {
bc := setUpTest ( BeaconNodeTester . TestConfig , "10" )
BeaconNodeTester . SetupBeaconNodeMock ( BeaconNodeTester . TestEvents , BeaconNodeTester . TestConfig . protocol , BeaconNodeTester . TestConfig . address , BeaconNodeTester . TestConfig . port , BeaconNodeTester . TestConfig . dummyParentRoot )
defer httpmock . DeactivateAndReset ( )
BeaconNodeTester . testKnownGapsMessages ( bc , 100 , 1 , maxRetry , BeaconNodeTester . TestEvents [ "100" ] . HeadMessage )
start , end := queryKnownGaps ( bc . Db , "11" , "99" )
Expect ( start ) . To ( Equal ( 11 ) )
Expect ( end ) . To ( Equal ( 99 ) )
} )
} )
Context ( "There is a gap at start up spanning multiple incrementing range." , func ( ) {
It ( "Should add multiple entries to the knownGaps table." , func ( ) {
bc := setUpTest ( BeaconNodeTester . TestConfig , "5" )
BeaconNodeTester . SetupBeaconNodeMock ( BeaconNodeTester . TestEvents , BeaconNodeTester . TestConfig . protocol , BeaconNodeTester . TestConfig . address , BeaconNodeTester . TestConfig . port , BeaconNodeTester . TestConfig . dummyParentRoot )
defer httpmock . DeactivateAndReset ( )
BeaconNodeTester . testKnownGapsMessages ( bc , 10 , 10 , maxRetry , BeaconNodeTester . TestEvents [ "100" ] . HeadMessage )
start , end := queryKnownGaps ( bc . Db , "6" , "16" )
Expect ( start ) . To ( Equal ( 6 ) )
Expect ( end ) . To ( Equal ( 16 ) )
start , end = queryKnownGaps ( bc . Db , "96" , "99" )
Expect ( start ) . To ( Equal ( 96 ) )
Expect ( end ) . To ( Equal ( 99 ) )
} )
} )
Context ( "Gaps between two head messages" , func ( ) {
It ( "Should add the slots in-between" , func ( ) {
bc := setUpTest ( BeaconNodeTester . TestConfig , "99" )
BeaconNodeTester . SetupBeaconNodeMock ( BeaconNodeTester . TestEvents , BeaconNodeTester . TestConfig . protocol , BeaconNodeTester . TestConfig . address , BeaconNodeTester . TestConfig . port , BeaconNodeTester . TestConfig . dummyParentRoot )
defer httpmock . DeactivateAndReset ( )
BeaconNodeTester . testKnownGapsMessages ( bc , 1000000 , 3 , maxRetry , BeaconNodeTester . TestEvents [ "100" ] . HeadMessage , BeaconNodeTester . TestEvents [ "2375703" ] . HeadMessage )
start , end := queryKnownGaps ( bc . Db , "101" , "1000101" )
Expect ( start ) . To ( Equal ( 101 ) )
Expect ( end ) . To ( Equal ( 1000101 ) )
start , end = queryKnownGaps ( bc . Db , "2000101" , "2375702" )
Expect ( start ) . To ( Equal ( 2000101 ) )
Expect ( end ) . To ( Equal ( 2375702 ) )
} )
} )
2022-05-09 18:44:27 +00:00
} )
2022-05-12 13:52:13 +00:00
Describe ( "ReOrg Scenario" , Label ( "unit" , "behavioral" ) , func ( ) {
Context ( "Altair: Multiple head messages for the same slot." , func ( ) {
It ( "The previous block should be marked as 'forked', the new block should be the only one marked as 'proposed'." , func ( ) {
2022-05-17 20:05:15 +00:00
bc := setUpTest ( BeaconNodeTester . TestConfig , "2375702" )
BeaconNodeTester . SetupBeaconNodeMock ( BeaconNodeTester . TestEvents , BeaconNodeTester . TestConfig . protocol , BeaconNodeTester . TestConfig . address , BeaconNodeTester . TestConfig . port , BeaconNodeTester . TestConfig . dummyParentRoot )
defer httpmock . DeactivateAndReset ( )
BeaconNodeTester . testMultipleHead ( bc , TestEvents [ "2375703" ] . HeadMessage , TestEvents [ "2375703-dummy" ] . HeadMessage , 74240 , maxRetry )
2022-05-12 13:52:13 +00:00
} )
} )
Context ( "Phase0: Multiple head messages for the same slot." , func ( ) {
It ( "The previous block should be marked as 'forked', the new block should be the only one marked as 'proposed'." , func ( ) {
2022-05-17 20:05:15 +00:00
bc := setUpTest ( BeaconNodeTester . TestConfig , "99" )
BeaconNodeTester . SetupBeaconNodeMock ( BeaconNodeTester . TestEvents , BeaconNodeTester . TestConfig . protocol , BeaconNodeTester . TestConfig . address , BeaconNodeTester . TestConfig . port , BeaconNodeTester . TestConfig . dummyParentRoot )
defer httpmock . DeactivateAndReset ( )
BeaconNodeTester . testMultipleHead ( bc , TestEvents [ "100-dummy" ] . HeadMessage , TestEvents [ "100" ] . HeadMessage , 3 , maxRetry )
2022-05-12 13:52:13 +00:00
} )
} )
2022-05-17 20:05:15 +00:00
Context ( "Phase 0: Multiple reorgs have occurred on this slot" , func ( ) {
2022-05-12 13:52:13 +00:00
It ( "The previous blocks should be marked as 'forked', the new block should be the only one marked as 'proposed'." , func ( ) {
2022-05-17 20:05:15 +00:00
bc := setUpTest ( BeaconNodeTester . TestConfig , "99" )
BeaconNodeTester . SetupBeaconNodeMock ( BeaconNodeTester . TestEvents , BeaconNodeTester . TestConfig . protocol , BeaconNodeTester . TestConfig . address , BeaconNodeTester . TestConfig . port , BeaconNodeTester . TestConfig . dummyParentRoot )
defer httpmock . DeactivateAndReset ( )
BeaconNodeTester . testMultipleReorgs ( bc , TestEvents [ "100-dummy" ] . HeadMessage , TestEvents [ "100-dummy-2" ] . HeadMessage , TestEvents [ "100" ] . HeadMessage , 3 , maxRetry )
2022-05-12 13:52:13 +00:00
} )
} )
2022-05-24 20:18:55 +00:00
Context ( "Altair: Multiple reorgs have occurred on this slot" , func ( ) {
2022-05-12 13:52:13 +00:00
It ( "The previous blocks should be marked as 'forked', the new block should be the only one marked as 'proposed'." , func ( ) {
2022-05-17 20:05:15 +00:00
bc := setUpTest ( BeaconNodeTester . TestConfig , "2375702" )
BeaconNodeTester . SetupBeaconNodeMock ( BeaconNodeTester . TestEvents , BeaconNodeTester . TestConfig . protocol , BeaconNodeTester . TestConfig . address , BeaconNodeTester . TestConfig . port , BeaconNodeTester . TestConfig . dummyParentRoot )
defer httpmock . DeactivateAndReset ( )
BeaconNodeTester . testMultipleReorgs ( bc , TestEvents [ "2375703-dummy" ] . HeadMessage , TestEvents [ "2375703-dummy-2" ] . HeadMessage , TestEvents [ "2375703" ] . HeadMessage , 74240 , maxRetry )
2022-05-12 13:52:13 +00:00
} )
} )
} )
2022-05-09 18:44:27 +00:00
} )
2022-05-12 13:52:13 +00:00
type Config struct {
2022-05-12 19:44:05 +00:00
protocol string
address string
port int
dummyParentRoot string
dbHost string
dbPort int
dbName string
dbUser string
dbPassword string
dbDriver string
knownGapsTableIncrement int
2022-06-03 16:47:13 +00:00
bcUniqueIdentifier int
2022-06-06 13:02:43 +00:00
checkDb bool
2022-05-09 18:44:27 +00:00
}
2022-05-12 13:52:13 +00:00
//////////////////////////////////////////////////////
// Helper functions
//////////////////////////////////////////////////////
// Must run before each test. We can't use the beforeEach because of the way
// Gingko treats race conditions.
2022-05-17 20:05:15 +00:00
func setUpTest ( config Config , maxSlot string ) * beaconclient . BeaconClient {
2022-06-06 13:02:43 +00:00
bc , err := beaconclient . CreateBeaconClient ( context . Background ( ) , config . protocol , config . address , config . port , config . knownGapsTableIncrement , config . bcUniqueIdentifier , config . checkDb )
2022-06-03 16:47:13 +00:00
Expect ( err ) . ToNot ( HaveOccurred ( ) )
2022-05-12 13:52:13 +00:00
db , err := postgres . SetupPostgresDb ( config . dbHost , config . dbPort , config . dbName , config . dbUser , config . dbPassword , config . dbDriver )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
// Drop all records from the DB.
clearEthclDbTables ( db )
2022-05-17 20:05:15 +00:00
// Add an slot to the ethcl.slots table so it we can control how known_gaps are handled.
writeSlot ( db , maxSlot )
2022-05-12 13:52:13 +00:00
bc . Db = db
2022-06-03 16:47:13 +00:00
return bc
2022-05-09 18:44:27 +00:00
}
2022-05-17 20:05:15 +00:00
// A helper function to validate the expected output from the ethcl.slots table.
func validateSlot ( bc * beaconclient . BeaconClient , headMessage beaconclient . Head , correctEpoch int , correctStatus string ) {
2022-05-12 13:52:13 +00:00
epoch , dbSlot , blockRoot , stateRoot , status := queryDbSlotAndBlock ( bc . Db , headMessage . Slot , headMessage . Block )
2022-06-08 15:14:01 +00:00
log . Info ( "validateSlot: " , headMessage )
2022-05-12 13:52:13 +00:00
baseSlot , err := strconv . Atoi ( headMessage . Slot )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
Expect ( dbSlot ) . To ( Equal ( baseSlot ) )
Expect ( epoch ) . To ( Equal ( correctEpoch ) )
Expect ( blockRoot ) . To ( Equal ( headMessage . Block ) )
Expect ( stateRoot ) . To ( Equal ( headMessage . State ) )
Expect ( status ) . To ( Equal ( correctStatus ) )
}
2022-05-17 20:05:15 +00:00
// A helper function to validate the expected output from the ethcl.signed_beacon_block table.
func validateSignedBeaconBlock ( bc * beaconclient . BeaconClient , headMessage beaconclient . Head , correctParentRoot string , correctEth1BlockHash string , correctMhKey string ) {
dbSlot , blockRoot , parentRoot , eth1BlockHash , mhKey := queryDbSignedBeaconBlock ( bc . Db , headMessage . Slot , headMessage . Block )
2022-06-08 15:14:01 +00:00
log . Info ( "validateSignedBeaconBlock: " , headMessage )
2022-05-17 20:05:15 +00:00
baseSlot , err := strconv . Atoi ( headMessage . Slot )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
Expect ( dbSlot ) . To ( Equal ( baseSlot ) )
Expect ( blockRoot ) . To ( Equal ( headMessage . Block ) )
2022-06-08 15:14:01 +00:00
Expect ( parentRoot ) . To ( Equal ( correctParentRoot ) )
Expect ( eth1BlockHash ) . To ( Equal ( correctEth1BlockHash ) )
Expect ( mhKey ) . To ( Equal ( correctMhKey ) )
2022-05-17 20:05:15 +00:00
}
// A helper function to validate the expected output from the ethcl.beacon_state table.
func validateBeaconState ( bc * beaconclient . BeaconClient , headMessage beaconclient . Head , correctMhKey string ) {
dbSlot , stateRoot , mhKey := queryDbBeaconState ( bc . Db , headMessage . Slot , headMessage . State )
2022-06-08 15:14:01 +00:00
log . Info ( "validateBeaconState: " , headMessage )
2022-05-17 20:05:15 +00:00
baseSlot , err := strconv . Atoi ( headMessage . Slot )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
Expect ( dbSlot ) . To ( Equal ( baseSlot ) )
Expect ( stateRoot ) . To ( Equal ( headMessage . State ) )
2022-06-08 15:14:01 +00:00
Expect ( mhKey ) . To ( Equal ( correctMhKey ) )
2022-05-17 20:05:15 +00:00
}
2022-05-12 13:52:13 +00:00
// Wrapper function to send a head message to the beaconclient
2022-05-17 20:05:15 +00:00
func sendHeadMessage ( bc * beaconclient . BeaconClient , head beaconclient . Head , maxRetry int , expectedSuccessfulInserts uint64 ) {
2022-05-12 13:52:13 +00:00
data , err := json . Marshal ( head )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
2022-05-24 20:18:55 +00:00
startInserts := atomic . LoadUint64 ( & bc . Metrics . SlotInserts )
2022-05-12 13:52:13 +00:00
bc . HeadTracking . MessagesCh <- & sse . Event {
ID : [ ] byte { } ,
Data : data ,
Event : [ ] byte { } ,
Retry : [ ] byte { } ,
}
2022-05-13 14:46:13 +00:00
curRetry := 0
2022-05-24 20:18:55 +00:00
for atomic . LoadUint64 ( & bc . Metrics . SlotInserts ) != startInserts + expectedSuccessfulInserts {
2022-05-12 13:52:13 +00:00
time . Sleep ( 1 * time . Second )
2022-05-13 14:46:13 +00:00
curRetry = curRetry + 1
if curRetry == maxRetry {
2022-05-17 20:05:15 +00:00
log . WithFields ( log . Fields {
2022-05-24 20:18:55 +00:00
"startInsert" : startInserts ,
"expectedSuccessfulInserts" : expectedSuccessfulInserts ,
"currentValue" : atomic . LoadUint64 ( & bc . Metrics . SlotInserts ) ,
2022-05-17 20:05:15 +00:00
} ) . Error ( "HeadTracking Insert wasn't incremented properly." )
Fail ( "Too many retries have occurred." )
2022-05-13 14:46:13 +00:00
}
2022-05-09 18:44:27 +00:00
}
2022-05-12 13:52:13 +00:00
}
// A helper function to query the ethcl.slots table based on the slot and block_root
func queryDbSlotAndBlock ( db sql . Database , querySlot string , queryBlockRoot string ) ( int , int , string , string , string ) {
sqlStatement := ` SELECT epoch, slot, block_root, state_root, status FROM ethcl.slots WHERE slot=$1 AND block_root=$2; `
var epoch , slot int
var blockRoot , stateRoot , status string
2022-05-24 20:18:55 +00:00
log . Debug ( "Starting to query the ethcl.slots table, " , querySlot , " " , queryBlockRoot )
err := db . QueryRow ( context . Background ( ) , sqlStatement , querySlot , queryBlockRoot ) . Scan ( & epoch , & slot , & blockRoot , & stateRoot , & status )
2022-05-12 13:52:13 +00:00
Expect ( err ) . ToNot ( HaveOccurred ( ) )
2022-05-24 20:18:55 +00:00
log . Debug ( "Querying the ethcl.slots table complete" )
2022-05-12 13:52:13 +00:00
return epoch , slot , blockRoot , stateRoot , status
}
2022-05-17 20:05:15 +00:00
// A helper function to query the ethcl.signed_beacon_block table based on the slot and block_root.
func queryDbSignedBeaconBlock ( db sql . Database , querySlot string , queryBlockRoot string ) ( int , string , string , string , string ) {
sqlStatement := ` SELECT slot, block_root, parent_block_root, eth1_block_hash, mh_key FROM ethcl.signed_beacon_block WHERE slot=$1 AND block_root=$2; `
var slot int
var blockRoot , parent_block_root , eth1_block_hash , mh_key string
row := db . QueryRow ( context . Background ( ) , sqlStatement , querySlot , queryBlockRoot )
err := row . Scan ( & slot , & blockRoot , & parent_block_root , & eth1_block_hash , & mh_key )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
return slot , blockRoot , parent_block_root , eth1_block_hash , mh_key
}
// A helper function to query the ethcl.signed_beacon_block table based on the slot and block_root.
func queryDbBeaconState ( db sql . Database , querySlot string , queryStateRoot string ) ( int , string , string ) {
sqlStatement := ` SELECT slot, state_root, mh_key FROM ethcl.beacon_state WHERE slot=$1 AND state_root=$2; `
var slot int
var stateRoot , mh_key string
row := db . QueryRow ( context . Background ( ) , sqlStatement , querySlot , queryStateRoot )
err := row . Scan ( & slot , & stateRoot , & mh_key )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
return slot , stateRoot , mh_key
}
// Count the entries in the knownGaps table.
func countKnownGapsTable ( db sql . Database ) int {
var count int
sqlStatement := "SELECT COUNT(*) FROM ethcl.known_gaps"
err := db . QueryRow ( context . Background ( ) , sqlStatement ) . Scan ( & count )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
return count
}
// Return the start and end slot
func queryKnownGaps ( db sql . Database , queryStartGap string , QueryEndGap string ) ( int , int ) {
sqlStatement := ` SELECT start_slot, end_slot FROM ethcl.known_gaps WHERE start_slot=$1 AND end_slot=$2; `
var startGap , endGap int
row := db . QueryRow ( context . Background ( ) , sqlStatement , queryStartGap , QueryEndGap )
err := row . Scan ( & startGap , & endGap )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
return startGap , endGap
}
2022-05-12 13:52:13 +00:00
// A function that will remove all entries from the ethcl tables for you.
func clearEthclDbTables ( db sql . Database ) {
2022-06-08 15:14:01 +00:00
deleteQueries := [ ] string { "DELETE FROM ethcl.slots;" , "DELETE FROM ethcl.signed_beacon_block;" , "DELETE FROM ethcl.beacon_state;" , "DELETE FROM ethcl.known_gaps;" , "DELETE FROM ethcl.historic_process;" , "DELETE FROM public.blocks;" }
2022-05-12 13:52:13 +00:00
for _ , queries := range deleteQueries {
_ , err := db . Exec ( context . Background ( ) , queries )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
}
2022-05-17 20:05:15 +00:00
}
2022-05-12 13:52:13 +00:00
2022-05-17 20:05:15 +00:00
// Write an entry to the ethcl.slots table with just a slot number
func writeSlot ( db sql . Database , slot string ) {
_ , err := db . Exec ( context . Background ( ) , beaconclient . UpsertSlotsStmt , "0" , slot , "" , "" , "" )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
}
// Read a file with the SignedBeaconBlock in SSZ and return the SSZ object. This is used for testing only.
// We can't use the readSignedBeaconBlockInterface to update struct fields so this is the workaround.
func readSignedBeaconBlock ( slotFile string ) ( * st . SignedBeaconBlock , error ) {
dat , err := os . ReadFile ( slotFile )
if err != nil {
return nil , fmt . Errorf ( "Can't find the slot file, %s" , slotFile )
}
block := & st . SignedBeaconBlock { }
err = block . UnmarshalSSZ ( dat )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
return block , nil
}
// Read a file with the SignedBeaconBlock in SSZ and return the SSZ object. This is used for testing only.
// We can't use the readSignedBeaconBlockInterface to update struct fields so this is the workaround.
func readSignedBeaconBlockAltair ( slotFile string ) ( * st . SignedBeaconBlockAltair , error ) {
dat , err := os . ReadFile ( slotFile )
if err != nil {
return nil , fmt . Errorf ( "Can't find the slot file, %s" , slotFile )
}
block := & st . SignedBeaconBlockAltair { }
err = block . UnmarshalSSZ ( dat )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
return block , nil
}
// Read a file with the SignedBeaconBlock in SSZ and return the SSZ objects interface. This is production like.
// It will provide the correct struct for the given fork.
func readSignedBeaconBlockInterface ( slotFile string , vm * dt . VersionedUnmarshaler ) ( si . SignedBeaconBlock , error ) {
dat , err := os . ReadFile ( slotFile )
if err != nil {
return nil , fmt . Errorf ( "Can't find the slot file, %s" , slotFile )
}
block , err := vm . UnmarshalBeaconBlock ( dat )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
return block , nil
}
// Read a file with the BeaconState in SSZ and return the SSZ object
func readBeaconState ( slotFile string ) ( state . BeaconState , * dt . VersionedUnmarshaler , error ) {
dat , err := os . ReadFile ( slotFile )
if err != nil {
return nil , nil , fmt . Errorf ( "Can't find the slot file, %s" , slotFile )
}
versionedUnmarshaler , err := dt . FromState ( dat )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
state , err := versionedUnmarshaler . UnmarshalBeaconState ( dat )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
return state , versionedUnmarshaler , nil
2022-05-12 13:52:13 +00:00
}
// An object that is used to aggregate test functions. Test functions are needed because we need to
// run the same tests on multiple blocks for multiple forks. So they save us time.
type TestBeaconNode struct {
TestEvents map [ string ] Message
TestConfig Config
}
// Create a new new mock for the beacon node.
func ( tbc TestBeaconNode ) SetupBeaconNodeMock ( TestEvents map [ string ] Message , protocol string , address string , port int , dummyParentRoot string ) {
httpmock . Activate ( )
stateUrl := ` =~^ ` + protocol + "://" + address + ":" + strconv . Itoa ( port ) + beaconclient . BcStateQueryEndpoint + ` ([^/]+)\z `
httpmock . RegisterResponder ( "GET" , stateUrl ,
func ( req * http . Request ) ( * http . Response , error ) {
// Get ID from request
id := httpmock . MustGetSubmatch ( req , 1 )
dat , err := tbc . provideSsz ( id , "state" , dummyParentRoot )
if err != nil {
2022-06-08 14:26:27 +00:00
return httpmock . NewStringResponse ( 404 , fmt . Sprintf ( "Unable to find file for %s" , id ) ) , nil
2022-05-12 13:52:13 +00:00
}
return httpmock . NewBytesResponse ( 200 , dat ) , nil
} ,
)
blockUrl := ` =~^ ` + protocol + "://" + address + ":" + strconv . Itoa ( port ) + beaconclient . BcBlockQueryEndpoint + ` ([^/]+)\z `
httpmock . RegisterResponder ( "GET" , blockUrl ,
func ( req * http . Request ) ( * http . Response , error ) {
// Get ID from request
id := httpmock . MustGetSubmatch ( req , 1 )
dat , err := tbc . provideSsz ( id , "block" , dummyParentRoot )
if err != nil {
2022-06-08 14:26:27 +00:00
return httpmock . NewStringResponse ( 404 , fmt . Sprintf ( "Unable to find file for %s" , id ) ) , nil
2022-05-12 13:52:13 +00:00
}
return httpmock . NewBytesResponse ( 200 , dat ) , nil
} ,
)
2022-05-24 20:18:55 +00:00
// Not needed but could be useful to have.
blockRootUrl := ` =~^ ` + protocol + "://" + address + ":" + strconv . Itoa ( port ) + "/eth/v1/beacon/blocks/" + ` ([^/]+) ` + "/root"
httpmock . RegisterResponder ( "GET" , blockRootUrl ,
func ( req * http . Request ) ( * http . Response , error ) {
// Get ID from request
slot := httpmock . MustGetSubmatch ( req , 1 )
dat , err := tbc . provideBlockRoot ( slot )
if err != nil {
Expect ( err ) . NotTo ( HaveOccurred ( ) )
return httpmock . NewStringResponse ( 404 , fmt . Sprintf ( "Unable to find block root for %s" , slot ) ) , err
}
return httpmock . NewBytesResponse ( 200 , dat ) , nil
} ,
)
}
// Provide the Block root
func ( tbc TestBeaconNode ) provideBlockRoot ( slot string ) ( [ ] byte , error ) {
for _ , val := range tbc . TestEvents {
if val . HeadMessage . Slot == slot && val . MimicConfig == nil {
block , err := hex . DecodeString ( val . HeadMessage . Block [ 2 : ] )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
return block , nil
}
}
return nil , fmt . Errorf ( "Unable to find the Blockroot in test object." )
2022-05-09 18:44:27 +00:00
}
// A function to mimic querying the state from the beacon node. We simply get the SSZ file are return it.
2022-05-12 13:52:13 +00:00
func ( tbc TestBeaconNode ) provideSsz ( slotIdentifier string , sszIdentifier string , dummyParentRoot string ) ( [ ] byte , error ) {
var slotFile string
var Message Message
for _ , val := range tbc . TestEvents {
if sszIdentifier == "state" {
2022-05-24 20:18:55 +00:00
if ( val . HeadMessage . Slot == slotIdentifier && val . MimicConfig == nil ) || val . HeadMessage . State == slotIdentifier {
2022-05-12 13:52:13 +00:00
slotFile = val . BeaconState
Message = val
}
} else if sszIdentifier == "block" {
2022-05-24 20:18:55 +00:00
if ( val . HeadMessage . Slot == slotIdentifier && val . MimicConfig == nil ) || val . HeadMessage . Block == slotIdentifier {
2022-05-12 13:52:13 +00:00
slotFile = val . SignedBeaconBlock
Message = val
}
}
}
if Message . MimicConfig != nil {
log . Info ( "We are going to create a custom SSZ object for testing purposes." )
if sszIdentifier == "block" {
2022-05-17 20:05:15 +00:00
// A dirty solution to handle different Block Types.
// * I was unsuccessful in implementing generics.
// * I can't use the interfaces.SignedBeaconBlock
// * I was short on time.
// * This solution allows us to hardcode the version and create the write block type for it when we
// Are mimicing an existing block.
switch Message . MimicConfig . ForkVersion {
case "phase0" :
block , err := readSignedBeaconBlock ( slotFile )
if err != nil {
return nil , err
}
slot , err := strconv . ParseUint ( Message . HeadMessage . Slot , 10 , 64 )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
block . Block . Slot = types . Slot ( slot )
2022-05-12 13:52:13 +00:00
2022-05-17 20:05:15 +00:00
block . Block . StateRoot , err = hex . DecodeString ( Message . HeadMessage . State )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
2022-05-12 13:52:13 +00:00
2022-05-17 20:05:15 +00:00
if Message . MimicConfig . ParentRoot == "" {
block . Block . ParentRoot , err = hex . DecodeString ( dummyParentRoot )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
} else {
block . Block . ParentRoot , err = hex . DecodeString ( Message . MimicConfig . ParentRoot )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
}
return block . MarshalSSZ ( )
case "altair" :
block , err := readSignedBeaconBlockAltair ( slotFile )
if err != nil {
return nil , err
}
slot , err := strconv . ParseUint ( Message . HeadMessage . Slot , 10 , 64 )
2022-05-12 13:52:13 +00:00
Expect ( err ) . ToNot ( HaveOccurred ( ) )
2022-05-17 20:05:15 +00:00
block . Block . Slot = types . Slot ( slot )
block . Block . StateRoot , err = hex . DecodeString ( Message . HeadMessage . State )
2022-05-12 13:52:13 +00:00
Expect ( err ) . ToNot ( HaveOccurred ( ) )
2022-05-17 20:05:15 +00:00
if Message . MimicConfig . ParentRoot == "" {
block . Block . ParentRoot , err = hex . DecodeString ( dummyParentRoot )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
} else {
block . Block . ParentRoot , err = hex . DecodeString ( Message . MimicConfig . ParentRoot )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
}
return block . MarshalSSZ ( )
2022-05-12 13:52:13 +00:00
}
}
if sszIdentifier == "state" {
2022-05-17 20:05:15 +00:00
state , _ , err := readBeaconState ( slotFile )
2022-05-12 13:52:13 +00:00
if err != nil {
2022-05-17 20:05:15 +00:00
return nil , err
2022-05-12 13:52:13 +00:00
}
slot , err := strconv . ParseUint ( Message . HeadMessage . Slot , 10 , 64 )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
2022-05-17 20:05:15 +00:00
err = state . SetSlot ( types . Slot ( slot ) )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
2022-05-12 13:52:13 +00:00
return state . MarshalSSZ ( )
}
}
if slotFile == "" {
return nil , fmt . Errorf ( "We couldn't find the slot file for %s" , slotIdentifier )
}
2022-05-09 18:44:27 +00:00
dat , err := os . ReadFile ( slotFile )
if err != nil {
2022-05-12 13:52:13 +00:00
return nil , fmt . Errorf ( "Can't find the slot file, %s" , slotFile )
}
return dat , nil
}
// Helper function to test three reorg messages. There are going to be many functions like this,
// Because we need to test the same logic for multiple phases.
2022-05-17 20:05:15 +00:00
func ( tbc TestBeaconNode ) testMultipleReorgs ( bc * beaconclient . BeaconClient , firstHead beaconclient . Head , secondHead beaconclient . Head , thirdHead beaconclient . Head , epoch int , maxRetry int ) {
2022-05-24 20:18:55 +00:00
go bc . CaptureHead ( )
2022-05-12 13:52:13 +00:00
time . Sleep ( 1 * time . Second )
2022-05-24 20:18:55 +00:00
log . Info ( "Sending Messages to BeaconClient" )
2022-05-17 20:05:15 +00:00
sendHeadMessage ( bc , firstHead , maxRetry , 1 )
sendHeadMessage ( bc , secondHead , maxRetry , 1 )
sendHeadMessage ( bc , thirdHead , maxRetry , 1 )
2022-05-12 13:52:13 +00:00
2022-05-13 14:46:13 +00:00
curRetry := 0
2022-05-24 20:18:55 +00:00
for atomic . LoadUint64 ( & bc . Metrics . ReorgInserts ) != 2 {
2022-05-12 13:52:13 +00:00
time . Sleep ( 1 * time . Second )
2022-05-13 14:46:13 +00:00
curRetry = curRetry + 1
if curRetry == maxRetry {
2022-05-17 20:05:15 +00:00
Fail ( " Too many retries have occurred." )
2022-05-13 14:46:13 +00:00
}
2022-05-12 13:52:13 +00:00
}
2022-05-17 20:05:15 +00:00
log . Info ( "Checking to make sure the fork was marked properly." )
validateSlot ( bc , firstHead , epoch , "forked" )
validateSlot ( bc , secondHead , epoch , "forked" )
validateSlot ( bc , thirdHead , epoch , "proposed" )
2022-05-12 13:52:13 +00:00
log . Info ( "Send the reorg message." )
data , err := json . Marshal ( & beaconclient . ChainReorg {
Slot : firstHead . Slot ,
Depth : "1" ,
OldHeadBlock : thirdHead . Block ,
NewHeadBlock : secondHead . Block ,
OldHeadState : thirdHead . State ,
NewHeadState : secondHead . State ,
Epoch : strconv . Itoa ( epoch ) ,
ExecutionOptimistic : false ,
} )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
bc . ReOrgTracking . MessagesCh <- & sse . Event {
Data : data ,
}
2022-05-13 14:46:13 +00:00
curRetry = 0
2022-05-24 20:18:55 +00:00
for atomic . LoadUint64 ( & bc . Metrics . ReorgInserts ) != 3 {
2022-05-12 13:52:13 +00:00
time . Sleep ( 1 * time . Second )
2022-05-13 14:46:13 +00:00
curRetry = curRetry + 1
if curRetry == maxRetry {
2022-05-17 20:05:15 +00:00
Fail ( "Too many retries have occurred." )
2022-05-13 14:46:13 +00:00
}
2022-05-09 18:44:27 +00:00
}
2022-05-12 13:52:13 +00:00
2022-05-24 20:18:55 +00:00
if bc . Metrics . KnownGapsInserts != 0 {
2022-05-17 20:05:15 +00:00
Fail ( "We found gaps when processing a single block" )
}
2022-05-12 13:52:13 +00:00
log . Info ( "Make sure the forks were properly updated!" )
2022-05-17 20:05:15 +00:00
validateSlot ( bc , firstHead , epoch , "forked" )
validateSlot ( bc , secondHead , epoch , "proposed" )
validateSlot ( bc , thirdHead , epoch , "forked" )
2022-05-12 13:52:13 +00:00
}
// A test to validate a single block was processed correctly
2022-05-17 20:05:15 +00:00
func ( tbc TestBeaconNode ) testProcessBlock ( bc * beaconclient . BeaconClient , head beaconclient . Head , epoch int , maxRetry int , expectedSuccessInsert uint64 , expectedKnownGaps uint64 , expectedReorgs uint64 ) {
2022-05-24 20:18:55 +00:00
go bc . CaptureHead ( )
2022-05-12 13:52:13 +00:00
time . Sleep ( 1 * time . Second )
2022-05-17 20:05:15 +00:00
sendHeadMessage ( bc , head , maxRetry , expectedSuccessInsert )
curRetry := 0
2022-05-24 20:18:55 +00:00
for atomic . LoadUint64 ( & bc . Metrics . KnownGapsInserts ) != expectedKnownGaps {
2022-05-17 20:05:15 +00:00
time . Sleep ( 1 * time . Second )
curRetry = curRetry + 1
if curRetry == maxRetry {
2022-05-24 20:18:55 +00:00
Fail ( fmt . Sprintf ( "Wrong gap metrics, got: %d, wanted %d" , bc . Metrics . KnownGapsInserts , expectedKnownGaps ) )
2022-05-17 20:05:15 +00:00
}
}
curRetry = 0
2022-05-24 20:18:55 +00:00
for atomic . LoadUint64 ( & bc . Metrics . ReorgInserts ) != expectedReorgs {
2022-05-17 20:05:15 +00:00
time . Sleep ( 1 * time . Second )
curRetry = curRetry + 1
if curRetry == maxRetry {
2022-05-24 20:18:55 +00:00
Fail ( fmt . Sprintf ( "Wrong reorg metrics, got: %d, wanted %d" , bc . Metrics . KnownGapsInserts , expectedKnownGaps ) )
2022-05-17 20:05:15 +00:00
}
}
if expectedSuccessInsert > 0 {
validateSlot ( bc , head , epoch , "proposed" )
}
2022-05-12 13:52:13 +00:00
}
// A test that ensures that if two HeadMessages occur for a single slot they are marked
// as proposed and forked correctly.
2022-05-17 20:05:15 +00:00
func ( tbc TestBeaconNode ) testMultipleHead ( bc * beaconclient . BeaconClient , firstHead beaconclient . Head , secondHead beaconclient . Head , epoch int , maxRetry int ) {
2022-05-24 20:18:55 +00:00
go bc . CaptureHead ( )
2022-05-12 13:52:13 +00:00
time . Sleep ( 1 * time . Second )
2022-05-17 20:05:15 +00:00
sendHeadMessage ( bc , firstHead , maxRetry , 1 )
sendHeadMessage ( bc , secondHead , maxRetry , 1 )
2022-05-12 13:52:13 +00:00
2022-05-13 14:46:13 +00:00
curRetry := 0
2022-05-24 20:18:55 +00:00
for atomic . LoadUint64 ( & bc . Metrics . ReorgInserts ) != 1 {
2022-05-12 13:52:13 +00:00
time . Sleep ( 1 * time . Second )
2022-05-13 14:46:13 +00:00
curRetry = curRetry + 1
if curRetry == maxRetry {
2022-05-17 20:05:15 +00:00
Fail ( " Too many retries have occurred." )
2022-05-13 14:46:13 +00:00
}
2022-05-12 13:52:13 +00:00
}
2022-05-24 20:18:55 +00:00
if bc . Metrics . KnownGapsInserts != 0 {
2022-05-17 20:05:15 +00:00
Fail ( "We found gaps when processing a single block" )
}
2022-05-12 13:52:13 +00:00
log . Info ( "Checking Altair to make sure the fork was marked properly." )
2022-05-17 20:05:15 +00:00
validateSlot ( bc , firstHead , epoch , "forked" )
validateSlot ( bc , secondHead , epoch , "proposed" )
}
// A test that ensures that if two HeadMessages occur for a single slot they are marked
// as proposed and forked correctly.
func ( tbc TestBeaconNode ) testKnownGapsMessages ( bc * beaconclient . BeaconClient , tableIncrement int , expectedEntries uint64 , maxRetry int , msg ... beaconclient . Head ) {
2022-05-24 20:18:55 +00:00
bc . KnownGapTableIncrement = tableIncrement
go bc . CaptureHead ( )
2022-05-17 20:05:15 +00:00
time . Sleep ( 1 * time . Second )
for _ , headMsg := range msg {
sendHeadMessage ( bc , headMsg , maxRetry , 1 )
}
curRetry := 0
2022-05-24 20:18:55 +00:00
for atomic . LoadUint64 ( & bc . Metrics . KnownGapsInserts ) != expectedEntries {
2022-05-17 20:05:15 +00:00
time . Sleep ( 1 * time . Second )
curRetry = curRetry + 1
if curRetry == maxRetry {
Fail ( "Too many retries have occurred." )
}
}
log . Info ( "Checking to make sure we have the expected number of entries in the knownGaps table." )
knownGapCount := countKnownGapsTable ( bc . Db )
Expect ( knownGapCount ) . To ( Equal ( int ( expectedEntries ) ) )
2022-05-24 20:18:55 +00:00
if atomic . LoadUint64 ( & bc . Metrics . ReorgInserts ) != 0 {
2022-05-17 20:05:15 +00:00
Fail ( "We found reorgs when we didn't expect it" )
}
}
// This function will make sure we are properly able to get the SszRoot of the SignedBeaconBlock and the BeaconState.
func testSszRoot ( msg Message ) {
state , vm , err := readBeaconState ( msg . BeaconState )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
stateRoot , err := state . HashTreeRoot ( context . Background ( ) )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
Expect ( msg . HeadMessage . State ) . To ( Equal ( "0x" + hex . EncodeToString ( stateRoot [ : ] ) ) )
block , err := readSignedBeaconBlockInterface ( msg . SignedBeaconBlock , vm )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
blockRoot , err := block . Block ( ) . HashTreeRoot ( )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
Expect ( msg . HeadMessage . Block ) . To ( Equal ( "0x" + hex . EncodeToString ( blockRoot [ : ] ) ) )
2022-05-09 18:44:27 +00:00
}