github.com/adoriasoft/tendermint@v0.34.0-dev1.0.20200722151356-96d84601a75a/statesync/stateprovider.go (about) 1 package statesync 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 8 dbm "github.com/tendermint/tm-db" 9 10 "github.com/tendermint/tendermint/libs/log" 11 tmsync "github.com/tendermint/tendermint/libs/sync" 12 "github.com/tendermint/tendermint/light" 13 lightprovider "github.com/tendermint/tendermint/light/provider" 14 lighthttp "github.com/tendermint/tendermint/light/provider/http" 15 lightrpc "github.com/tendermint/tendermint/light/rpc" 16 lightdb "github.com/tendermint/tendermint/light/store/db" 17 tmstate "github.com/tendermint/tendermint/proto/tendermint/state" 18 rpchttp "github.com/tendermint/tendermint/rpc/client/http" 19 sm "github.com/tendermint/tendermint/state" 20 "github.com/tendermint/tendermint/types" 21 ) 22 23 //go:generate mockery -case underscore -name StateProvider 24 25 // StateProvider is a provider of trusted state data for bootstrapping a node. This refers 26 // to the state.State object, not the state machine. 27 type StateProvider interface { 28 // AppHash returns the app hash after the given height has been committed. 29 AppHash(height uint64) ([]byte, error) 30 // Commit returns the commit at the given height. 31 Commit(height uint64) (*types.Commit, error) 32 // State returns a state object at the given height. 33 State(height uint64) (sm.State, error) 34 } 35 36 // lightClientStateProvider is a state provider using the light client. 37 type lightClientStateProvider struct { 38 tmsync.Mutex // light.Client is not concurrency-safe 39 lc *light.Client 40 version tmstate.Version 41 providers map[lightprovider.Provider]string 42 } 43 44 // NewLightClientStateProvider creates a new StateProvider using a light client and RPC clients. 45 func NewLightClientStateProvider( 46 chainID string, 47 version tmstate.Version, 48 servers []string, 49 trustOptions light.TrustOptions, 50 logger log.Logger, 51 ) (StateProvider, error) { 52 if len(servers) < 2 { 53 return nil, fmt.Errorf("at least 2 RPC servers are required, got %v", len(servers)) 54 } 55 56 providers := make([]lightprovider.Provider, 0, len(servers)) 57 providerRemotes := make(map[lightprovider.Provider]string) 58 for _, server := range servers { 59 client, err := rpcClient(server) 60 if err != nil { 61 return nil, fmt.Errorf("failed to set up RPC client: %w", err) 62 } 63 provider := lighthttp.NewWithClient(chainID, client) 64 providers = append(providers, provider) 65 // We store the RPC addresses keyed by provider, so we can find the address of the primary 66 // provider used by the light client and use it to fetch consensus parameters. 67 providerRemotes[provider] = server 68 } 69 70 lc, err := light.NewClient(chainID, trustOptions, providers[0], providers[1:], 71 lightdb.New(dbm.NewMemDB(), ""), light.Logger(logger), light.MaxRetryAttempts(5)) 72 if err != nil { 73 return nil, err 74 } 75 return &lightClientStateProvider{ 76 lc: lc, 77 version: version, 78 providers: providerRemotes, 79 }, nil 80 } 81 82 // AppHash implements StateProvider. 83 func (s *lightClientStateProvider) AppHash(height uint64) ([]byte, error) { 84 s.Lock() 85 defer s.Unlock() 86 87 // We have to fetch the next height, which contains the app hash for the previous height. 88 header, err := s.lc.VerifyHeaderAtHeight(int64(height+1), time.Now()) 89 if err != nil { 90 return nil, err 91 } 92 return header.AppHash, nil 93 } 94 95 // Commit implements StateProvider. 96 func (s *lightClientStateProvider) Commit(height uint64) (*types.Commit, error) { 97 s.Lock() 98 defer s.Unlock() 99 header, err := s.lc.VerifyHeaderAtHeight(int64(height), time.Now()) 100 if err != nil { 101 return nil, err 102 } 103 return header.Commit, nil 104 } 105 106 // State implements StateProvider. 107 func (s *lightClientStateProvider) State(height uint64) (sm.State, error) { 108 s.Lock() 109 defer s.Unlock() 110 111 state := sm.State{ 112 ChainID: s.lc.ChainID(), 113 Version: s.version, 114 } 115 116 // We need to verify up until h+2, to get the validator set. This also prefetches the headers 117 // for h and h+1 in the typical case where the trusted header is after the snapshot height. 118 _, err := s.lc.VerifyHeaderAtHeight(int64(height+2), time.Now()) 119 if err != nil { 120 return sm.State{}, err 121 } 122 header, err := s.lc.VerifyHeaderAtHeight(int64(height), time.Now()) 123 if err != nil { 124 return sm.State{}, err 125 } 126 nextHeader, err := s.lc.VerifyHeaderAtHeight(int64(height+1), time.Now()) 127 if err != nil { 128 return sm.State{}, err 129 } 130 state.LastBlockHeight = header.Height 131 state.LastBlockTime = header.Time 132 state.LastBlockID = header.Commit.BlockID 133 state.AppHash = nextHeader.AppHash 134 state.LastResultsHash = nextHeader.LastResultsHash 135 136 state.LastValidators, _, err = s.lc.TrustedValidatorSet(int64(height)) 137 if err != nil { 138 return sm.State{}, err 139 } 140 state.Validators, _, err = s.lc.TrustedValidatorSet(int64(height + 1)) 141 if err != nil { 142 return sm.State{}, err 143 } 144 state.NextValidators, _, err = s.lc.TrustedValidatorSet(int64(height + 2)) 145 if err != nil { 146 return sm.State{}, err 147 } 148 state.LastHeightValidatorsChanged = int64(height) 149 150 // We'll also need to fetch consensus params via RPC, using light client verification. 151 primaryURL, ok := s.providers[s.lc.Primary()] 152 if !ok || primaryURL == "" { 153 return sm.State{}, fmt.Errorf("could not find address for primary light client provider") 154 } 155 primaryRPC, err := rpcClient(primaryURL) 156 if err != nil { 157 return sm.State{}, fmt.Errorf("unable to create RPC client: %w", err) 158 } 159 rpcclient := lightrpc.NewClient(primaryRPC, s.lc) 160 result, err := rpcclient.ConsensusParams(&nextHeader.Height) 161 if err != nil { 162 return sm.State{}, fmt.Errorf("unable to fetch consensus parameters for height %v: %w", 163 nextHeader.Height, err) 164 } 165 state.ConsensusParams = result.ConsensusParams 166 167 return state, nil 168 } 169 170 // rpcClient sets up a new RPC client 171 func rpcClient(server string) (*rpchttp.HTTP, error) { 172 if !strings.Contains(server, "://") { 173 server = "http://" + server 174 } 175 c, err := rpchttp.New(server, "/websocket") 176 if err != nil { 177 return nil, err 178 } 179 return c, nil 180 }