Skip to content

Commit

Permalink
solana failover (#10)
Browse files Browse the repository at this point in the history
* solana failover

* fix endpoints

* fix simulate (versioned transaction)

* fix cache reset

* update web3-blockchains dependency

* v10.9.0: adds solana failover and getProviders (plural)
  • Loading branch information
10xSebastian authored May 2, 2023
1 parent 3e58504 commit 5126960
Show file tree
Hide file tree
Showing 19 changed files with 507 additions and 246 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,25 @@ mock({
// ...
```

If you use the `fastest` request strategy, make sure to mock all providers:


```javascript
import { getProviders } from '@depay/web3-client'
import { mock } from '@depay/web3-mock'

const providers = await getProviders('ethereum')

providers.forEach((provider)=>{
mock({
provider,
blockchain: 'ethereum'
})
})

// ...
```

## URL

You can either use a URL like `<blockchain>://<address>/<method>` that gets deconstructed internally or you pass a deconstructed object directly:
Expand Down
26 changes: 22 additions & 4 deletions dist/esm/index.evm.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,11 @@ const setProviderEndpoints$1 = async (blockchain, endpoints)=> {

getAllProviders()[blockchain] = endpoints.map((endpoint, index)=>
new StaticJsonRpcBatchProvider(endpoint, blockchain, endpoints, ()=>{
getAllProviders()[blockchain].splice(index, 1);
if(getAllProviders()[blockchain].length === 1) {
setProviderEndpoints$1(blockchain, endpoints);
} else {
getAllProviders()[blockchain].splice(index, 1);
}
})
);

Expand Down Expand Up @@ -220,7 +224,7 @@ const getProvider$1 = async (blockchain)=> {
return await window._Web3ClientGetProviderPromise[blockchain]
};

const getProviders = async(blockchain)=>{
const getProviders$1 = async(blockchain)=>{

let providers = getAllProviders();
if(providers && providers[blockchain]){ return providers[blockchain] }
Expand All @@ -239,7 +243,7 @@ const getProviders = async(blockchain)=>{

var EVM = {
getProvider: getProvider$1,
getProviders,
getProviders: getProviders$1,
setProviderEndpoints: setProviderEndpoints$1,
setProvider: setProvider$1,
};
Expand Down Expand Up @@ -267,6 +271,7 @@ let resetCache = () => {
getWindow()._Web3ClientCacheStore = {};
getWindow()._Web3ClientPromiseStore = {};
getWindow()._Web3ClientProviders = {};
getWindow()._Web3ClientGetProviderPromise = undefined;
};

let set = function ({ key, value, expires }) {
Expand Down Expand Up @@ -365,6 +370,19 @@ const getProvider = async (blockchain)=>{
}
};

const getProviders = async (blockchain)=>{

if(supported.evm.includes(blockchain)) {


return await EVM.getProviders(blockchain)


} else if(supported.solana.includes(blockchain)) ; else {
throw 'Unknown blockchain: ' + blockchain
}
};

const setProvider = (blockchain, provider)=>{

resetCache();
Expand Down Expand Up @@ -529,4 +547,4 @@ const request = async function (url, options) {

const simulate = undefined;

export { estimate, getProvider, request, resetCache, setProvider, setProviderEndpoints, simulate };
export { estimate, getProvider, getProviders, request, resetCache, setProvider, setProviderEndpoints, simulate };
132 changes: 87 additions & 45 deletions dist/esm/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Connection, Buffer, PublicKey, TransactionInstruction, Transaction, ACCOUNT_LAYOUT } from '@depay/solana-web3.js';
import { Connection, Buffer, PublicKey, TransactionInstruction, TransactionMessage, VersionedTransaction, ACCOUNT_LAYOUT } from '@depay/solana-web3.js';
import Blockchains from '@depay/web3-blockchains';
import { ethers } from 'ethers';

Expand Down Expand Up @@ -133,7 +133,11 @@ const setProviderEndpoints$2 = async (blockchain, endpoints)=> {

getAllProviders$1()[blockchain] = endpoints.map((endpoint, index)=>
new StaticJsonRpcBatchProvider(endpoint, blockchain, endpoints, ()=>{
getAllProviders$1()[blockchain].splice(index, 1);
if(getAllProviders$1()[blockchain].length === 1) {
setProviderEndpoints$2(blockchain, endpoints);
} else {
getAllProviders$1()[blockchain].splice(index, 1);
}
})
);

Expand Down Expand Up @@ -192,7 +196,7 @@ const getProvider$2 = async (blockchain)=> {
return await window._Web3ClientGetProviderPromise[blockchain]
};

const getProviders$1 = async(blockchain)=>{
const getProviders$2 = async(blockchain)=>{

let providers = getAllProviders$1();
if(providers && providers[blockchain]){ return providers[blockchain] }
Expand All @@ -211,7 +215,7 @@ const getProviders$1 = async(blockchain)=>{

var EVM = {
getProvider: getProvider$2,
getProviders: getProviders$1,
getProviders: getProviders$2,
setProviderEndpoints: setProviderEndpoints$2,
setProvider: setProvider$2,
};
Expand Down Expand Up @@ -246,9 +250,7 @@ const setProvider$1 = (blockchain, provider)=> {
const setProviderEndpoints$1 = async (blockchain, endpoints)=> {

getAllProviders()[blockchain] = endpoints.map((endpoint, index)=>
new StaticJsonRpcSequentialProvider(endpoint, blockchain, endpoints, ()=>{
getAllProviders()[blockchain].splice(index, 1);
})
new StaticJsonRpcSequentialProvider(endpoint, blockchain, endpoints)
);

let provider;
Expand Down Expand Up @@ -306,7 +308,7 @@ const getProvider$1 = async (blockchain)=> {
return await window._Web3ClientGetProviderPromise[blockchain]
};

const getProviders = async(blockchain)=>{
const getProviders$1 = async(blockchain)=>{

let providers = getAllProviders();
if(providers && providers[blockchain]){ return providers[blockchain] }
Expand All @@ -325,7 +327,7 @@ const getProviders = async(blockchain)=>{

var Solana = {
getProvider: getProvider$1,
getProviders,
getProviders: getProviders$1,
setProviderEndpoints: setProviderEndpoints$1,
setProvider: setProvider$1,
};
Expand Down Expand Up @@ -353,6 +355,7 @@ let resetCache = () => {
getWindow()._Web3ClientCacheStore = {};
getWindow()._Web3ClientPromiseStore = {};
getWindow()._Web3ClientProviders = {};
getWindow()._Web3ClientGetProviderPromise = undefined;
};

let set = function ({ key, value, expires }) {
Expand Down Expand Up @@ -457,6 +460,25 @@ const getProvider = async (blockchain)=>{
}
};

const getProviders = async (blockchain)=>{

if(supported.evm.includes(blockchain)) {


return await EVM.getProviders(blockchain)


} else if(supported.solana.includes(blockchain)) {


return await Solana.getProviders(blockchain)


} else {
throw 'Unknown blockchain: ' + blockchain
}
};

const setProvider = (blockchain, provider)=>{

resetCache();
Expand Down Expand Up @@ -518,13 +540,20 @@ let simulate = async function ({ blockchain, from, to, keys, api, params }) {
data
});

let transaction = new Transaction({ feePayer: new PublicKey(from) });
transaction.add(instruction);
const instructions = [];
instructions.push(instruction);

const messageV0 = new TransactionMessage({
payerKey: new PublicKey(from),
instructions,
}).compileToV0Message();

const transactionV0 = new VersionedTransaction(messageV0);

let result;
try{
const provider = await getProvider('solana');
result = await provider.simulateTransaction(transaction);
result = await provider.simulateTransaction(transactionV0);
} catch (error) {
console.log(error);
}
Expand Down Expand Up @@ -655,54 +684,67 @@ const balance = ({ address, provider }) => {
return provider.getBalance(new PublicKey(address))
};

const singleRequest = ({ blockchain, address, api, method, params, block, provider })=> {
const singleRequest = async({ blockchain, address, api, method, params, block, provider, providers })=> {

if(method == undefined || method === 'getAccountInfo') {
if(api == undefined) {
api = ACCOUNT_LAYOUT;
}
return accountInfo({ address, api, method, params, provider, block })
} else if(method === 'getProgramAccounts') {
return provider.getProgramAccounts(new PublicKey(address), params).then((accounts)=>{
if(api){
return accounts.map((account)=>{
account.data = api.decode(account.account.data);
return account
})
} else {
return accounts
try {

if(method == undefined || method === 'getAccountInfo') {
if(api == undefined) {
api = ACCOUNT_LAYOUT;
}
})
} else if(method === 'getTokenAccountBalance') {
return provider.getTokenAccountBalance(new PublicKey(address))
} else if (method === 'latestBlockNumber') {
return provider.getBlockHeight()
} else if (method === 'balance') {
return balance({ address, provider })
return await accountInfo({ address, api, method, params, provider, block })
} else if(method === 'getProgramAccounts') {
return await provider.getProgramAccounts(new PublicKey(address), params).then((accounts)=>{
if(api){
return accounts.map((account)=>{
account.data = api.decode(account.account.data);
return account
})
} else {
return accounts
}
})
} else if(method === 'getTokenAccountBalance') {
return await provider.getTokenAccountBalance(new PublicKey(address))
} else if (method === 'latestBlockNumber') {
return await provider.getBlockHeight()
} else if (method === 'balance') {
return await balance({ address, provider })
}

} catch (error){
if(providers && error && [
'Failed to fetch', '504', '503', '502', '500', '429', '426', '422', '413', '409', '408', '406', '405', '404', '403', '402', '401', '400'
].some((errorType)=>error.toString().match(errorType))) {
let nextProvider = providers[providers.indexOf(provider)+1] || providers[0];
return singleRequest({ blockchain, address, api, method, params, block, provider: nextProvider, providers })
} else {
throw error
}
}
};


var requestSolana = async ({ blockchain, address, api, method, params, block, timeout, strategy = 'fallback' }) => {

const providers = await Solana.getProviders(blockchain);

if(strategy === 'fastest') {

return Promise.race((await Solana.getProviders(blockchain)).map((provider)=>{
return Promise.race(providers.map((provider)=>{

const request = singleRequest({ blockchain, address, api, method, params, block, provider });
const succeedingRequest = new Promise((resolve)=>{
singleRequest({ blockchain, address, api, method, params, block, provider }).then(resolve);
}); // failing requests are ignored during race/fastest

if(timeout) {
const timeoutPromise = new Promise((_, reject)=>setTimeout(()=>{ reject(new Error("Web3ClientTimeout")); }, timeout));
return Promise.race([request, timeoutPromise])
} else {
return request
}
const timeoutPromise = new Promise((_, reject)=>setTimeout(()=>{ reject(new Error("Web3ClientTimeout")); }, timeout || 10000));

return Promise.race([succeedingRequest, timeoutPromise])
}))

} else { // failover

const provider = await Solana.getProvider(blockchain);
const request = singleRequest({ blockchain, address, api, method, params, block, provider });
const request = singleRequest({ blockchain, address, api, method, params, block, provider, providers });

if(timeout) {
timeout = new Promise((_, reject)=>setTimeout(()=>{ reject(new Error("Web3ClientTimeout")); }, timeout));
Expand Down Expand Up @@ -768,4 +810,4 @@ const request = async function (url, options) {
})
};

export { estimate, getProvider, request, resetCache, setProvider, setProviderEndpoints, simulate };
export { estimate, getProvider, getProviders, request, resetCache, setProvider, setProviderEndpoints, simulate };
Loading

0 comments on commit 5126960

Please sign in to comment.