github.com/Finschia/ostracon@v1.1.5/statesync/stateprovider.go (about) 1 package statesync 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "time" 8 9 tmstate "github.com/tendermint/tendermint/proto/tendermint/state" 10 dbm "github.com/tendermint/tm-db" 11 12 "github.com/Finschia/ostracon/crypto/ed25519" 13 "github.com/Finschia/ostracon/libs/log" 14 tmsync "github.com/Finschia/ostracon/libs/sync" 15 "github.com/Finschia/ostracon/light" 16 lightprovider "github.com/Finschia/ostracon/light/provider" 17 lighthttp "github.com/Finschia/ostracon/light/provider/http" 18 lightrpc "github.com/Finschia/ostracon/light/rpc" 19 lightdb "github.com/Finschia/ostracon/light/store/db" 20 rpchttp "github.com/Finschia/ostracon/rpc/client/http" 21 sm "github.com/Finschia/ostracon/state" 22 "github.com/Finschia/ostracon/types" 23 "github.com/Finschia/ostracon/version" 24 ) 25 26 //go:generate ../scripts/mockery_generate.sh StateProvider 27 28 // StateProvider is a provider of trusted state data for bootstrapping a node. This refers 29 // to the state.State object, not the state machine. 30 type StateProvider interface { 31 // AppHash returns the app hash after the given height has been committed. 32 AppHash(ctx context.Context, height uint64) ([]byte, error) 33 // Commit returns the commit at the given height. 34 Commit(ctx context.Context, height uint64) (*types.Commit, error) 35 // State returns a state object at the given height. 36 State(ctx context.Context, height uint64) (sm.State, error) 37 } 38 39 // lightClientStateProvider is a state provider using the light client. 40 type lightClientStateProvider struct { 41 tmsync.Mutex // light.Client is not concurrency-safe 42 lc *light.Client 43 version tmstate.Version 44 initialHeight int64 45 providers map[lightprovider.Provider]string 46 } 47 48 // NewLightClientStateProvider creates a new StateProvider using a light client and RPC clients. 49 func NewLightClientStateProvider( 50 ctx context.Context, 51 chainID string, 52 version tmstate.Version, 53 initialHeight int64, 54 servers []string, 55 trustOptions light.TrustOptions, 56 logger log.Logger, 57 ) (StateProvider, error) { 58 servers = uniqServers(servers) 59 if len(servers) < 2 { 60 return nil, fmt.Errorf("at least 2 RPC servers are required, got %v", len(servers)) 61 } 62 63 providers := make([]lightprovider.Provider, 0, len(servers)) 64 providerRemotes := make(map[lightprovider.Provider]string) 65 for _, server := range servers { 66 client, err := rpcClient(server) 67 if err != nil { 68 return nil, fmt.Errorf("failed to set up RPC client: %w", err) 69 } 70 provider := lighthttp.NewWithClient(chainID, client) 71 providers = append(providers, provider) 72 // We store the RPC addresses keyed by provider, so we can find the address of the primary 73 // provider used by the light client and use it to fetch consensus parameters. 74 providerRemotes[provider] = server 75 } 76 77 lc, err := light.NewClient(ctx, chainID, trustOptions, providers[0], providers[1:], 78 lightdb.New(dbm.NewMemDB(), ""), light.Logger(logger), light.MaxRetryAttempts(5)) 79 if err != nil { 80 return nil, err 81 } 82 return &lightClientStateProvider{ 83 lc: lc, 84 version: version, 85 initialHeight: initialHeight, 86 providers: providerRemotes, 87 }, nil 88 } 89 90 func uniqServers(servers []string) []string { 91 size := len(servers) 92 if size < 2 { 93 return servers 94 } 95 serversSet := make(map[string]struct{}, size) 96 uniqServers := make([]string, 0, size) 97 for _, server := range servers { 98 if _, ok := serversSet[server]; !ok { 99 serversSet[server] = struct{}{} 100 uniqServers = append(uniqServers, server) 101 } 102 } 103 return uniqServers 104 } 105 106 // AppHash implements StateProvider. 107 func (s *lightClientStateProvider) AppHash(ctx context.Context, height uint64) ([]byte, error) { 108 s.Lock() 109 defer s.Unlock() 110 111 // We have to fetch the next height, which contains the app hash for the previous height. 112 header, err := s.lc.VerifyLightBlockAtHeight(ctx, int64(height+1), time.Now()) 113 if err != nil { 114 return nil, err 115 } 116 // We also try to fetch the blocks at height H and H+2, since we need these 117 // when building the state while restoring the snapshot. This avoids the race 118 // condition where we try to restore a snapshot before H+2 exists. 119 // 120 // FIXME This is a hack, since we can't add new methods to the interface without 121 // breaking it. We should instead have a Has(ctx, height) method which checks 122 // that the state provider has access to the necessary data for the height. 123 // We piggyback on AppHash() since it's called when adding snapshots to the pool. 124 _, err = s.lc.VerifyLightBlockAtHeight(ctx, int64(height+2), time.Now()) 125 if err != nil { 126 return nil, err 127 } 128 return header.AppHash, nil 129 } 130 131 // Commit implements StateProvider. 132 func (s *lightClientStateProvider) Commit(ctx context.Context, height uint64) (*types.Commit, error) { 133 s.Lock() 134 defer s.Unlock() 135 header, err := s.lc.VerifyLightBlockAtHeight(ctx, int64(height), time.Now()) 136 if err != nil { 137 return nil, err 138 } 139 return header.Commit, nil 140 } 141 142 // State implements StateProvider. 143 func (s *lightClientStateProvider) State(ctx context.Context, height uint64) (sm.State, error) { 144 s.Lock() 145 defer s.Unlock() 146 147 state := sm.State{ 148 ChainID: s.lc.ChainID(), 149 Version: s.version, 150 InitialHeight: s.initialHeight, 151 } 152 if state.InitialHeight == 0 { 153 state.InitialHeight = 1 154 } 155 156 // The snapshot height maps onto the state heights as follows: 157 // 158 // height: last block, i.e. the snapshotted height 159 // height+1: current block, i.e. the first block we'll process after the snapshot 160 // height+2: next block, i.e. the second block after the snapshot 161 // 162 // We need to fetch the NextValidators from height+2 because if the application changed 163 // the validator set at the snapshot height then this only takes effect at height+2. 164 lastLightBlock, err := s.lc.VerifyLightBlockAtHeight(ctx, int64(height), time.Now()) 165 if err != nil { 166 return sm.State{}, err 167 } 168 currentLightBlock, err := s.lc.VerifyLightBlockAtHeight(ctx, int64(height+1), time.Now()) 169 if err != nil { 170 return sm.State{}, err 171 } 172 nextLightBlock, err := s.lc.VerifyLightBlockAtHeight(ctx, int64(height+2), time.Now()) 173 if err != nil { 174 return sm.State{}, err 175 } 176 177 state.Version = tmstate.Version{ 178 Consensus: currentLightBlock.Version, 179 Software: version.OCCoreSemVer, 180 } 181 state.LastBlockHeight = lastLightBlock.Height 182 state.LastBlockTime = lastLightBlock.Time 183 state.LastBlockID = lastLightBlock.Commit.BlockID 184 state.AppHash = currentLightBlock.AppHash 185 state.LastResultsHash = currentLightBlock.LastResultsHash 186 state.LastValidators = lastLightBlock.ValidatorSet 187 state.Validators = currentLightBlock.ValidatorSet 188 state.NextValidators = nextLightBlock.ValidatorSet 189 state.LastHeightValidatorsChanged = nextLightBlock.Height 190 191 // We'll also need to fetch consensus params and last proof hash via RPC, using light client verification. 192 primaryURL, ok := s.providers[s.lc.Primary()] 193 if !ok || primaryURL == "" { 194 return sm.State{}, fmt.Errorf("could not find address for primary light client provider") 195 } 196 primaryRPC, err := rpcClient(primaryURL) 197 if err != nil { 198 return sm.State{}, fmt.Errorf("unable to create RPC client: %w", err) 199 } 200 rpcclient := lightrpc.NewClient(primaryRPC, s.lc) 201 202 resultConsensusParams, err := rpcclient.ConsensusParams(ctx, ¤tLightBlock.Height) 203 if err != nil { 204 return sm.State{}, fmt.Errorf("unable to fetch consensus parameters for height %v: %w", 205 nextLightBlock.Height, err) 206 } 207 state.ConsensusParams = resultConsensusParams.ConsensusParams 208 state.Version.Consensus.App = state.ConsensusParams.Version.AppVersion 209 state.LastHeightConsensusParamsChanged = currentLightBlock.Height 210 211 resultBlock, err := rpcclient.Block(ctx, &lastLightBlock.Height) 212 if err != nil { 213 return sm.State{}, fmt.Errorf("unable to fetch block for height %v: %w", 214 lastLightBlock.Height, err) 215 } 216 proofHash, err := ed25519.ProofToHash(resultBlock.Block.Proof.Bytes()) 217 if err != nil { 218 return sm.State{}, err 219 } 220 state.LastProofHash = proofHash 221 return state, nil 222 } 223 224 // rpcClient sets up a new RPC client 225 func rpcClient(server string) (*rpchttp.HTTP, error) { 226 if !strings.Contains(server, "://") { 227 server = "http://" + server 228 } 229 c, err := rpchttp.New(server, "/websocket") 230 if err != nil { 231 return nil, err 232 } 233 return c, nil 234 }