@@ -30,6 +30,7 @@ import (
3030 "github.com/spf13/viper"
3131
3232 beaconAPI "github.com/oasisprotocol/oasis-core/go/beacon/api"
33+ "github.com/oasisprotocol/oasis-core/go/common"
3334 "github.com/oasisprotocol/oasis-core/go/common/cbor"
3435 "github.com/oasisprotocol/oasis-core/go/common/crypto/hash"
3536 "github.com/oasisprotocol/oasis-core/go/common/errors"
@@ -67,6 +68,8 @@ const (
6768 // tmSubscriberID is the subscriber identifier used for all internal CometBFT pubsub
6869 // subscriptions. If any other subscriber IDs need to be derived they will be under this prefix.
6970 tmSubscriberID = "oasis-core"
71+
72+ exportsSubDir = "exports"
7073)
7174
7275var (
@@ -764,40 +767,13 @@ func (t *fullService) lazyInit() error { // nolint: gocyclo
764767 t .client = cmtcli .New (t .node )
765768 t .failMonitor = newFailMonitor (t .ctx , t .Logger , t .node .ConsensusState ().Wait )
766769
767- // Register a halt hook that handles upgrades gracefully.
768- t .RegisterHaltHook (func (_ context.Context , _ int64 , _ beaconAPI.EpochTime , err error ) {
769- if ! errors .Is (err , upgradeAPI .ErrStopForUpgrade ) {
770- return
771- }
772-
773- // Mark this as a clean shutdown and request the node to stop gracefully.
774- t .failMonitor .markCleanShutdown ()
775-
776- // Wait before stopping to give time for P2P messages to propagate. Sleep for at least
777- // minUpgradeStopWaitPeriod or the configured commit timeout.
778- t .Logger .Info ("waiting a bit before stopping the node for upgrade" )
779- waitPeriod := minUpgradeStopWaitPeriod
780- if tc := t .timeoutCommit ; tc > waitPeriod {
781- waitPeriod = tc
782- }
783- time .Sleep (waitPeriod )
784-
785- go func () {
786- // Sleep another period so there is some time between when consensus shuts down and
787- // when all the other services start shutting down.
788- //
789- // Randomize the period so that not all nodes shut down at the same time.
790- delay := random .GetRandomValueFromInterval (0.5 , rand .Float64 (), config .GlobalConfig .Consensus .UpgradeStopDelay )
791- time .Sleep (delay )
792-
793- t .Logger .Info ("stopping the node for upgrade" )
794- t .Stop ()
795-
796- // Close the quit channel early to force the node to stop. This is needed because
797- // the CometBFT node will otherwise never quit.
798- close (t .quitCh )
799- }()
800- })
770+ hooks := []api.HaltHook {
771+ t .upgradeHaltHook (),
772+ t .dumpGenesisHaltHook (),
773+ }
774+ for _ , hook := range hooks {
775+ t .mux .RegisterHaltHook (hook )
776+ }
801777
802778 return nil
803779 }
@@ -915,6 +891,84 @@ func (t *fullService) metrics() {
915891 }
916892}
917893
894+ // upgradeHaltHook returns a halt hook that handles upgrades gracefully.
895+ func (t * fullService ) upgradeHaltHook () api.HaltHook {
896+ return func (_ context.Context , _ int64 , _ beaconAPI.EpochTime , err error ) {
897+ if ! errors .Is (err , upgradeAPI .ErrStopForUpgrade ) {
898+ return
899+ }
900+
901+ // Mark this as a clean shutdown and request the node to stop gracefully.
902+ t .failMonitor .markCleanShutdown ()
903+
904+ // Wait before stopping to give time for P2P messages to propagate. Sleep for at least
905+ // minUpgradeStopWaitPeriod or the configured commit timeout.
906+ t .Logger .Info ("waiting a bit before stopping the node for upgrade" )
907+ waitPeriod := minUpgradeStopWaitPeriod
908+ if tc := t .timeoutCommit ; tc > waitPeriod {
909+ waitPeriod = tc
910+ }
911+ time .Sleep (waitPeriod )
912+
913+ go func () {
914+ // Sleep another period so there is some time between when consensus shuts down and
915+ // when all the other services start shutting down.
916+ //
917+ // Randomize the period so that not all nodes shut down at the same time.
918+ delay := random .GetRandomValueFromInterval (0.5 , rand .Float64 (), config .GlobalConfig .Consensus .UpgradeStopDelay )
919+ time .Sleep (delay )
920+
921+ t .Logger .Info ("stopping the node for upgrade" )
922+ t .Stop ()
923+
924+ // Close the quit channel early to force the node to stop. This is needed because
925+ // the CometBFT node will otherwise never quit.
926+ close (t .quitCh )
927+ }()
928+ }
929+ }
930+
931+ // dumpGenesisHaltHook returns a halt hook which dump genesis.
932+ func (t * fullService ) dumpGenesisHaltHook () api.HaltHook {
933+ return func (ctx context.Context , height int64 , epoch beaconAPI.EpochTime , _ error ) {
934+ t .Logger .Info ("consensus halt hook: dumping genesis" ,
935+ "epoch" , epoch ,
936+ "height" , height ,
937+ )
938+ if err := t .dumpGenesis (ctx , height ); err != nil {
939+ t .Logger .Error ("halt hook: failed to dump genesis" ,
940+ "err" , err ,
941+ )
942+ return
943+ }
944+ t .Logger .Info ("consensus halt hook: genesis dumped" ,
945+ "epoch" , epoch ,
946+ "height" , height ,
947+ )
948+ }
949+ }
950+
951+ // dumpGenesis writes state at the given height to a genesis file.
952+ func (t * fullService ) dumpGenesis (ctx context.Context , height int64 ) error {
953+ doc , err := t .StateToGenesis (ctx , height )
954+ if err != nil {
955+ return fmt .Errorf ("dumpGenesis: failed to get genesis: %w" , err )
956+ }
957+
958+ exportsDir := filepath .Join (t .dataDir , exportsSubDir )
959+
960+ if err := common .Mkdir (exportsDir ); err != nil {
961+ return fmt .Errorf ("dumpGenesis: failed to create exports dir: %w" , err )
962+ }
963+
964+ filename := filepath .Join (exportsDir , fmt .Sprintf ("genesis-%s-at-%d.json" , doc .ChainID , doc .Height ))
965+ if err := doc .WriteFileJSON (filename ); err != nil {
966+ return fmt .Errorf ("dumpGenesis: failed to write genesis file: %w" , err )
967+ }
968+
969+ return nil
970+ }
971+
918972// New creates a new CometBFT consensus backend.
919973func New (ctx context.Context , cfg Config ) (consensusAPI.Backend , error ) {
920974 commonNode := newCommonNode (ctx , cfg .CommonConfig )
0 commit comments